Compare commits

...

34 commits
v0.7.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
52 changed files with 1099 additions and 701 deletions

View file

@ -1,12 +1,12 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: file
FormatStyle: 'file'
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,

View file

@ -36,7 +36,7 @@ add_compile_definitions(HYPRLOCK_VERSION="${VERSION}")
if (DEFINED HYPRLOCK_COMMIT)
add_compile_definitions(HYPRLOCK_COMMIT="${HYPRLOCK_COMMIT}")
else()
# get git commit
# get git commit
execute_process(
OUTPUT_VARIABLE GIT_SHORT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
@ -77,14 +77,11 @@ pkg_check_modules(
REQUIRED
IMPORTED_TARGET
wayland-client
wayland-protocols
wayland-protocols>=1.35
wayland-egl
hyprlang>=0.6.0
egl
xkbcommon
libjpeg
libwebp
libmagic
cairo
pangocairo
libdrm

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
@ -25,26 +29,23 @@ yay -S hyprlock-git # compiles from latest source
### Deps
You need the following dependencies
- wayland-client
- wayland-protocols
- mesa
- hyprwayland-scanner
And the development libraries for the following
- cairo
- libdrm
- pango
- xkbcommon
- pam
- hyprgraphics
- hyprland-protocols
- hyprlang
- hyprutils
- hyprgraphics
- libmagic (file-devel on Fedora)
- hyprwayland-scanner
- mesa (required is libgbm, libdrm and the opengl runtime)
- pam
- pango
- sdbus-cpp (>= 2.0.0)
- wayland-client
- wayland-protocols
- xkbcommon
Development libraries are usually suffixed with `-devel` or `-dev` in most distro repos.
You also need to install `mesa-libgbm-devel` on some distros like RPM based ones where its not
bundled with the mesa package.
Sometimes distro packages are missing required development files.
Such distros usually offer development versions of library package - commonly suffixed with `-devel` or `-dev`.
### Building

View file

@ -1 +1 @@
0.7.0
0.8.2

View file

@ -1,18 +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
}
input-field {
monitor =
fade_on_empty = false
animation = inputFieldDots, 1, 2, linear
}
background {
color = rgb(23, 39, 41)
monitor =
path = screenshot
blur_passes = 3
}
input-field {
monitor =
size = 20%, 5%
outline_thickness = 3
inner_color = rgba(0, 0, 0, 0.0) # no fill
outer_color = rgba(33ccffee) rgba(00ff99ee) 45deg
check_color = rgba(00ff99ee) rgba(ff6633ee) 120deg
fail_color = rgba(ff6633ee) rgba(ff0066ee) 40deg
font_color = rgb(143, 143, 143)
fade_on_empty = false
rounding = 15
font_family = $font
placeholder_text = Input password...
fail_text = $PAMFAIL
# uncomment to use a letter instead of a dot to indicate the typed password
# dots_text_format = *
# dots_size = 0.4
dots_spacing = 0.3
# uncomment to use an input indicator that does not show the password length (similar to swaylock's input indicator)
# hide_input = true
position = 0, -20
halign = center
valign = center
}
# TIME
label {
monitor =
text = $TIME # ref. https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/#variable-substitution
font_size = 90
font_family = $font
position = -30, 0
halign = right
valign = top
}
# DATE
label {
monitor =
text = cmd[update:60000] date +"%A, %d %B %Y" # update every 60 seconds
font_size = 25
font_family = $font
position = -30, -150
halign = right
valign = top
}
label {
monitor =
text = $LAYOUT[en,ru]
font_size = 24
onclick = hyprctl switchxkblayout all next
position = 250, -20
halign = center
valign = center
}

View file

@ -13,11 +13,11 @@
]
},
"locked": {
"lastModified": 1737634889,
"narHash": "sha256-9JZE3KxcXOqZH9zs3UeadngDiK/yIACTiAR8HSA/TNI=",
"lastModified": 1743953322,
"narHash": "sha256-prQ5JKopXtzCMX2eT3dXbaVvGmzjMRE2bXStQDdazpM=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "0d77b4895ad5f1bb3b0ee43103a5246c58b65591",
"rev": "9d7f2687c84c729afbc3b13f7937655570f2978d",
"type": "github"
},
"original": {
@ -39,11 +39,11 @@
]
},
"locked": {
"lastModified": 1737634606,
"narHash": "sha256-W7W87Cv6wqZ9PHegI6rH1+ve3zJPiyevMFf0/HwdbCQ=",
"lastModified": 1744468525,
"narHash": "sha256-9HySx+EtsbbKlZDlY+naqqOV679VdxP6x6fP3wxDXJk=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "f41271d35cc0f370d300413d756c2677f386af9d",
"rev": "f1000c54d266e6e4e9d646df0774fac5b8a652df",
"type": "github"
},
"original": {
@ -62,11 +62,11 @@
]
},
"locked": {
"lastModified": 1737978343,
"narHash": "sha256-TfFS0HCEJh63Kahrkp1h9hVDMdLU8a37Zz+IFucxyfA=",
"lastModified": 1743950287,
"narHash": "sha256-/6IAEWyb8gC/NKZElxiHChkouiUOrVYNq9YqG0Pzm4Y=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "6a8bc9d2a4451df12f5179dc0b1d2d46518a90ab",
"rev": "f2dc70e448b994cef627a157ee340135bd68fbc6",
"type": "github"
},
"original": {
@ -85,11 +85,11 @@
]
},
"locked": {
"lastModified": 1735493474,
"narHash": "sha256-fktzv4NaqKm94VAkAoVqO/nqQlw+X0/tJJNAeCSfzK4=",
"lastModified": 1739870480,
"narHash": "sha256-SiDN5BGxa/1hAsqhgJsS03C3t2QrLgBT8u+ENJ0Qzwc=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "de913476b59ee88685fdc018e77b8f6637a2ae0b",
"rev": "206367a08dc5ac4ba7ad31bdca391d098082e64b",
"type": "github"
},
"original": {
@ -100,11 +100,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1737469691,
"narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=",
"lastModified": 1744463964,
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab",
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
"type": "github"
},
"original": {

View file

@ -4,13 +4,10 @@
cmake,
pkg-config,
cairo,
file,
libdrm,
libGL,
libjpeg,
libwebp,
libxkbcommon,
mesa,
libgbm,
hyprgraphics,
hyprlang,
hyprutils,
@ -40,13 +37,10 @@ stdenv.mkDerivation {
buildInputs = [
cairo
file
libdrm
libGL
libjpeg
libwebp
libxkbcommon
mesa
libgbm
hyprgraphics
hyprlang
hyprutils

View file

@ -11,10 +11,10 @@
CAuth::CAuth() {
static const auto ENABLEPAM = g_pConfigManager->getValue<Hyprlang::INT>("auth:pam:enabled");
if (*ENABLEPAM)
m_vImpls.push_back(std::make_shared<CPam>());
m_vImpls.emplace_back(makeShared<CPam>());
static const auto ENABLEFINGERPRINT = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:enabled");
if (*ENABLEFINGERPRINT)
m_vImpls.push_back(std::make_shared<CFingerprint>());
m_vImpls.emplace_back(makeShared<CFingerprint>());
RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!");
}
@ -29,15 +29,12 @@ void CAuth::submitInput(const std::string& input) {
for (const auto& i : m_vImpls) {
i->handleInput(input);
}
g_pHyprlock->clearPasswordBuffer();
}
bool CAuth::checkWaiting() {
for (const auto& i : m_vImpls) {
if (i->checkWaiting())
return true;
}
return false;
return std::ranges::any_of(m_vImpls, [](const auto& i) { return i->checkWaiting(); });
}
const std::string& CAuth::getCurrentFailText() {
@ -64,7 +61,7 @@ size_t CAuth::getFailedAttempts() {
return m_sCurrentFail.failedAttempts;
}
std::shared_ptr<IAuthImplementation> CAuth::getImpl(eAuthImplementations implType) {
SP<IAuthImplementation> CAuth::getImpl(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i;
@ -79,30 +76,49 @@ void CAuth::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->clearPasswordBuffer();
g_pHyprlock->enqueueForceUpdateTimers();
g_pHyprlock->renderAllOutputs();
}
static void passwordUnlockCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->unlock();
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::enqueueUnlock() {
g_pHyprlock->addTimer(std::chrono::milliseconds(0), passwordUnlockCallback, nullptr);
void CAuth::resetDisplayFail() {
g_pAuth->m_bDisplayFailText = false;
m_resetDisplayFailTimer->cancel();
m_resetDisplayFailTimer.reset();
}

View file

@ -1,9 +1,11 @@
#pragma once
#include <memory>
#include <optional>
#include <vector>
#include "../defines.hpp"
#include "../core/Timer.hpp"
enum eAuthImplementations {
AUTH_IMPL_PAM = 0,
AUTH_IMPL_FINGERPRINT = 1,
@ -28,23 +30,25 @@ class CAuth {
public:
CAuth();
void start();
void start();
void submitInput(const std::string& input);
bool checkWaiting();
void submitInput(const std::string& input);
bool checkWaiting();
const std::string& getCurrentFailText();
const std::string& getCurrentFailText();
std::optional<std::string> getFailText(eAuthImplementations implType);
std::optional<std::string> getPrompt(eAuthImplementations implType);
size_t getFailedAttempts();
std::optional<std::string> getFailText(eAuthImplementations implType);
std::optional<std::string> getPrompt(eAuthImplementations implType);
size_t getFailedAttempts();
std::shared_ptr<IAuthImplementation> getImpl(eAuthImplementations implType);
SP<IAuthImplementation> getImpl(eAuthImplementations implType);
void terminate();
void terminate();
void enqueueUnlock();
void enqueueFail(const std::string& failText, eAuthImplementations implType);
void enqueueUnlock();
void enqueueFail(const std::string& failText, eAuthImplementations implType);
void resetDisplayFail();
// Should only be set via the main thread
bool m_bDisplayFailText = false;
@ -56,7 +60,8 @@ class CAuth {
size_t failedAttempts = 0;
} m_sCurrentFail;
std::vector<std::shared_ptr<IAuthImplementation>> m_vImpls;
std::vector<SP<IAuthImplementation>> m_vImpls;
std::shared_ptr<CTimer> m_resetDisplayFailTimer;
};
inline std::unique_ptr<CAuth> g_pAuth;
inline UP<CAuth> g_pAuth;

View file

@ -65,20 +65,13 @@ void CFingerprint::init() {
// When entering sleep, the wake signal will trigger startVerify().
if (m_sDBUSState.sleeping)
return;
inhibitSleep();
startVerify();
});
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) {
Debug::log(LOG, "fprint: PrepareForSleep (start: {})", start);
if (start) {
m_sDBUSState.sleeping = true;
stopVerify();
m_sDBUSState.inhibitLock.reset();
} else {
m_sDBUSState.sleeping = false;
inhibitSleep();
m_sDBUSState.sleeping = start;
if (!m_sDBUSState.sleeping && !m_sDBUSState.verifying)
startVerify();
}
});
}
@ -111,18 +104,6 @@ std::shared_ptr<sdbus::IConnection> CFingerprint::getConnection() {
return m_sDBUSState.connection;
}
void CFingerprint::inhibitSleep() {
m_sDBUSState.login->callMethodAsync("Inhibit")
.onInterface(LOGIN_MANAGER)
.withArguments("sleep", "hyprlock", "Fingerprint verifcation must be stopped before sleep", "delay")
.uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::UnixFd fd) {
if (e)
Debug::log(WARN, "fprint: could not inhibit sleep: {}", e->what());
else
m_sDBUSState.inhibitLock = fd;
});
}
bool CFingerprint::createDeviceProxy() {
auto proxy = sdbus::createProxy(*m_sDBUSState.connection, FPRINT, sdbus::ObjectPath{"/net/reactivated/Fprint/Manager"});
@ -163,8 +144,11 @@ void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
auto matchResult = s_mapStringToTestType[result];
bool authenticated = false;
bool retry = false;
if (m_sDBUSState.sleeping && matchResult != MATCH_DISCONNECTED)
if (m_sDBUSState.sleeping) {
stopVerify();
Debug::log(LOG, "fprint: device suspended");
return;
}
switch (matchResult) {
case MATCH_INVALID: Debug::log(WARN, "fprint: unknown status: {}", result); break;
case MATCH_NO_MATCH:
@ -211,6 +195,8 @@ void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
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;
@ -229,6 +215,7 @@ void CFingerprint::claimDevice() {
}
void CFingerprint::startVerify(bool isRetry) {
m_sDBUSState.verifying = true;
if (!m_sDBUSState.device) {
if (!createDeviceProxy())
return;
@ -256,6 +243,7 @@ void CFingerprint::startVerify(bool isRetry) {
}
bool CFingerprint::stopVerify() {
m_sDBUSState.verifying = false;
if (!m_sDBUSState.device)
return false;
try {

View file

@ -29,12 +29,12 @@ class CFingerprint : public IAuthImplementation {
std::shared_ptr<sdbus::IConnection> connection;
std::unique_ptr<sdbus::IProxy> login;
std::unique_ptr<sdbus::IProxy> device;
sdbus::UnixFd inhibitLock;
bool abort = false;
bool done = false;
int retries = 0;
bool sleeping = false;
bool abort = false;
bool done = false;
int retries = 0;
bool sleeping = false;
bool verifying = false;
} m_sDBUSState;
std::string m_sFingerprintReady;
@ -45,8 +45,6 @@ class CFingerprint : public IAuthImplementation {
void handleVerifyStatus(const std::string& result, const bool done);
void inhibitSleep();
bool createDeviceProxy();
void claimDevice();
void startVerify(bool isRetry = false);

View file

@ -138,14 +138,7 @@ bool CPam::auth() {
return true;
}
// clearing the input must be done from the main thread
static void clearInputTimerCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->clearPasswordBuffer();
}
void CPam::waitForInput() {
g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr);
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
m_bBlockInput = false;
m_sConversationState.waitingForPamAuth = false;

View file

@ -27,7 +27,7 @@ class ICustomConfigValueData {
class CLayoutValueData : public ICustomConfigValueData {
public:
CLayoutValueData() {};
CLayoutValueData() = default;
virtual ~CLayoutValueData() {};
virtual eConfigValueDataTypes getDataType() {

View file

@ -209,7 +209,9 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue(name, "shadow_passes", Hyprlang::INT{0}); \
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});
m_config.addConfigValue("general:disable_loading_bar", Hyprlang::INT{0});
#define CLICKABLE(name) m_config.addSpecialConfigValue(name, "onclick", Hyprlang::STRING{""});
m_config.addConfigValue("general:text_trim", Hyprlang::INT{1});
m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0});
m_config.addConfigValue("general:grace", Hyprlang::INT{0});
@ -217,6 +219,7 @@ void CConfigManager::init() {
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"});
@ -257,6 +260,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "zindex", Hyprlang::INT{0});
SHADOWABLE("shape");
CLICKABLE("shape");
m_config.addSpecialCategory("image", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("image", "monitor", Hyprlang::STRING{""});
@ -273,6 +277,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("image", "zindex", Hyprlang::INT{0});
SHADOWABLE("image");
CLICKABLE("image");
m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""});
@ -294,11 +299,11 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("input-field", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"<i>Input Password</i>"});
m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0});
m_config.addSpecialConfigValue("input-field", "hide_input_base_color", Hyprlang::INT{0xEE00FF99});
m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "check_color", GRADIENTCONFIG("0xFF22CC88"));
m_config.addSpecialConfigValue("input-field", "fail_color", GRADIENTCONFIG("0xFFCC2222"));
m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"<i>$FAIL</i>"});
m_config.addSpecialConfigValue("input-field", "fail_timeout", Hyprlang::INT{2000});
m_config.addSpecialConfigValue("input-field", "capslock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "numlock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "bothlock_color", GRADIENTCONFIG(""));
@ -314,12 +319,13 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("label", "font_size", Hyprlang::INT{16});
m_config.addSpecialConfigValue("label", "text", Hyprlang::STRING{"Sample Text"});
m_config.addSpecialConfigValue("label", "font_family", Hyprlang::STRING{"Sans"});
m_config.addSpecialConfigValue("label", "halign", Hyprlang::STRING{"none"});
m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"none"});
m_config.addSpecialConfigValue("label", "halign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("label", "valign", Hyprlang::STRING{"center"});
m_config.addSpecialConfigValue("label", "rotate", Hyprlang::FLOAT{0});
m_config.addSpecialConfigValue("label", "text_align", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("label", "zindex", Hyprlang::INT{0});
SHADOWABLE("label");
CLICKABLE("label");
m_config.registerHandler(&::handleSource, "source", {.allowFlags = false});
m_config.registerHandler(&::handleBezier, "bezier", {.allowFlags = false});
@ -356,6 +362,7 @@ void CConfigManager::init() {
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
#undef SHADOWABLE
#undef CLICKABLE
}
std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
@ -367,6 +374,8 @@ 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());
@ -414,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
@ -440,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
@ -470,11 +481,11 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"position", m_config.getSpecialConfigValue("input-field", "position", k.c_str())},
{"placeholder_text", m_config.getSpecialConfigValue("input-field", "placeholder_text", k.c_str())},
{"hide_input", m_config.getSpecialConfigValue("input-field", "hide_input", k.c_str())},
{"hide_input_base_color", m_config.getSpecialConfigValue("input-field", "hide_input_base_color", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("input-field", "rounding", k.c_str())},
{"check_color", m_config.getSpecialConfigValue("input-field", "check_color", k.c_str())},
{"fail_color", m_config.getSpecialConfigValue("input-field", "fail_color", k.c_str())},
{"fail_text", m_config.getSpecialConfigValue("input-field", "fail_text", k.c_str())},
{"fail_timeout", m_config.getSpecialConfigValue("input-field", "fail_timeout", k.c_str())},
{"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())},
@ -505,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
@ -603,6 +615,11 @@ std::optional<std::string> CConfigManager::handleAnimation(const std::string& co
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

View file

@ -5,7 +5,6 @@
#include <hyprlang.hpp>
#include <optional>
#include <vector>
#include <memory>
#include <unordered_map>
#include "../defines.hpp"
@ -41,4 +40,4 @@ class CConfigManager {
Hyprlang::CConfig m_config;
};
inline std::unique_ptr<CConfigManager> g_pConfigManager;
inline UP<CConfigManager> g_pConfigManager;

View file

@ -4,7 +4,6 @@
#include <hyprutils/animation/AnimatedVariable.hpp>
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/Math.hpp"
#include "../defines.hpp"
class CHyprlockAnimationManager : public Hyprutils::Animation::CAnimationManager {
@ -31,4 +30,4 @@ class CHyprlockAnimationManager : public Hyprutils::Animation::CAnimationManager
bool m_bTickScheduled = false;
};
inline std::unique_ptr<CHyprlockAnimationManager> g_pAnimationManager;
inline UP<CHyprlockAnimationManager> g_pAnimationManager;

View file

@ -9,12 +9,15 @@ CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
}
void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
if (!dev)
if (!g_pSeatManager->m_pPointer)
return;
if (!dev)
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
dev->sendSetShape(lastCursorSerial, shape);
}
void CCursorShape::hideCursor() {
g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0);
}
}

View file

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

View file

@ -11,13 +11,9 @@ CSessionLockSurface::~CSessionLockSurface() {
wl_egl_window_destroy(eglWindow);
}
CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) {
CSessionLockSurface::CSessionLockSurface(const SP<COutput>& pOutput) : m_outputRef(pOutput), m_outputID(pOutput->m_ID) {
surface = makeShared<CCWlSurface>(g_pHyprlock->getCompositor()->sendCreateSurface());
if (!surface) {
Debug::log(CRIT, "Couldn't create wl_surface");
exit(1);
}
RASSERT(surface, "Couldn't create wl_surface");
static const auto FRACTIONALSCALING = g_pConfigManager->getValue<Hyprlang::INT>("general:fractional_scaling");
const auto ENABLE_FSV1 = *FRACTIONALSCALING == 1 || /* auto enable */ (*FRACTIONALSCALING == 2);
@ -45,12 +41,8 @@ CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) {
if (!PVIEWPORTER)
Debug::log(LOG, "No viewporter support! Oops, won't be able to scale!");
lockSurface = makeShared<CCExtSessionLockSurfaceV1>(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), output->output->resource()));
if (!lockSurface) {
Debug::log(CRIT, "Couldn't create ext_session_lock_surface_v1");
exit(1);
}
lockSurface = makeShared<CCExtSessionLockSurfaceV1>(g_pHyprlock->getSessionLock()->sendGetLockSurface(surface->resource(), pOutput->m_wlOutput->resource()));
RASSERT(lockSurface, "Couldn't create ext_session_lock_surface_v1");
lockSurface->setConfigure([this](CCExtSessionLockSurfaceV1* r, uint32_t serial, uint32_t width, uint32_t height) { configure({(double)width, (double)height}, serial); });
}
@ -62,6 +54,8 @@ void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
const bool SAMESIZE = logicalSize == size_;
const bool SAMESCALE = appliedScale == fractionalScale;
const auto POUTPUT = m_outputRef.lock();
serial = serial_;
logicalSize = size_;
appliedScale = fractionalScale;
@ -71,8 +65,8 @@ void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
viewport->sendSetDestination(logicalSize.x, logicalSize.y);
surface->sendSetBufferScale(1);
} else {
size = size_ * output->scale;
surface->sendSetBufferScale(output->scale);
size = size_ * POUTPUT->scale;
surface->sendSetBufferScale(POUTPUT->scale);
}
if (!SAMESERIAL)
@ -84,27 +78,18 @@ void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
if (!eglWindow) {
eglWindow = wl_egl_window_create((wl_surface*)surface->resource(), size.x, size.y);
if (!eglWindow) {
Debug::log(CRIT, "Couldn't create eglWindow");
exit(1);
}
RASSERT(eglWindow, "Couldn't create eglWindow");
} else
wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0);
if (!eglSurface) {
eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr);
if (!eglSurface) {
Debug::log(CRIT, "Couldn't create eglSurface: {}", eglGetError());
// Clean up resources to prevent leaks
wl_egl_window_destroy(eglWindow);
eglWindow = nullptr;
exit(1); // Consider graceful exit or fallback
}
RASSERT(eglSurface, "Couldn't create eglSurface");
}
if (readyForFrame && !(SAMESIZE && SAMESCALE)) {
g_pRenderer->removeWidgetsFor(this);
Debug::log(LOG, "Reloading widgets");
Debug::log(LOG, "output {} changed, reloading widgets!", POUTPUT->stringPort);
g_pRenderer->reconfigureWidgetsFor(POUTPUT->m_ID);
}
readyForFrame = true;
@ -129,7 +114,10 @@ void CSessionLockSurface::render() {
if (g_pHyprlock->m_bTerminate)
return;
Debug::log(TRACE, "[{}] frame {}, Current fps: {:.2f}", output->stringPort, m_frames, 1000.f / (frameTime - m_lastFrameTime));
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;
@ -151,3 +139,7 @@ void CSessionLockSurface::onCallback() {
render();
}
}
SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
return surface;
}

View file

@ -14,21 +14,24 @@ class CRenderer;
class CSessionLockSurface {
public:
CSessionLockSurface(COutput* output);
CSessionLockSurface(const SP<COutput>& pOutput);
~CSessionLockSurface();
void configure(const Vector2D& size, uint32_t serial);
void configure(const Vector2D& size, uint32_t serial);
bool readyForFrame = false;
bool readyForFrame = false;
float fractionalScale = 1.0;
float fractionalScale = 1.0;
void render();
void onCallback();
void onScaleUpdate();
void render();
void onCallback();
void onScaleUpdate();
SP<CCWlSurface> getWlSurface();
private:
COutput* output = nullptr;
WP<COutput> m_outputRef;
OUTPUTID m_outputID = OUTPUT_INVALID;
SP<CCWlSurface> surface = nullptr;
SP<CCExtSessionLockSurfaceV1> lockSurface = nullptr;
uint32_t serial = 0;
@ -49,4 +52,5 @@ class CSessionLockSurface {
SP<CCWlCallback> frameCallback = nullptr;
friend class CRenderer;
};
friend class COutput;
};

View file

@ -1,32 +1,35 @@
#include "Output.hpp"
#include "../helpers/Log.hpp"
#include "hyprlock.hpp"
#include "../renderer/Renderer.hpp"
COutput::COutput(SP<CCWlOutput> output_, uint32_t name_) : name(name_), output(output_) {
output->setDescription([this](CCWlOutput* r, const char* description) {
void COutput::create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t _name) {
m_ID = _name;
m_wlOutput = pWlOutput;
m_self = pSelf;
m_wlOutput->setDescription([this](CCWlOutput* r, const char* description) {
stringDesc = description ? std::string{description} : "";
Debug::log(LOG, "output {} description {}", name, stringDesc);
Debug::log(LOG, "output {} description {}", m_ID, stringDesc);
});
output->setName([this](CCWlOutput* r, const char* name) {
m_wlOutput->setName([this](CCWlOutput* r, const char* name) {
stringName = std::string{name} + stringName;
stringPort = std::string{name};
Debug::log(LOG, "output {} name {}", name, name);
});
output->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; });
m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { scale = sc; });
output->setDone([this](CCWlOutput* r) {
m_wlOutput->setDone([this](CCWlOutput* r) {
done = true;
Debug::log(LOG, "output {} done", name);
if (g_pHyprlock->m_lockAquired && !sessionLockSurface) {
Debug::log(LOG, "output {} creating a new lock surface", name);
sessionLockSurface = std::make_unique<CSessionLockSurface>(this);
Debug::log(LOG, "output {} done", m_ID);
if (g_pHyprlock->m_lockAquired && !m_sessionLockSurface) {
Debug::log(LOG, "output {} creating a new lock surface", m_ID);
createSessionLockSurface();
}
});
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
m_wlOutput->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
// handle portrait mode and flipped cases
if (transform % 2 == 1)
size = {height, width};
@ -34,10 +37,33 @@ COutput::COutput(SP<CCWlOutput> output_, uint32_t name_) : name(name_), output(o
size = {width, height};
});
output->setGeometry(
m_wlOutput->setGeometry(
[this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) {
transform = (wl_output_transform)transform_;
Debug::log(LOG, "output {} make {} model {}", name, make ? make : "", model ? model : "");
Debug::log(LOG, "output {} make {} model {}", m_ID, make ? make : "", model ? model : "");
});
}
void COutput::createSessionLockSurface() {
if (!m_self.valid()) {
Debug::log(ERR, "output {} dead??", m_ID);
return;
}
if (m_sessionLockSurface) {
Debug::log(ERR, "output {} already has a session lock surface", m_ID);
return;
}
if (size == Vector2D{0, 0}) {
Debug::log(WARN, "output {} refusing to create a lock surface with size 0x0", m_ID);
return;
}
m_sessionLockSurface = makeUnique<CSessionLockSurface>(m_self.lock());
}
Vector2D COutput::getViewport() const {
return (m_sessionLockSurface) ? m_sessionLockSurface->size : size;
}

View file

@ -2,27 +2,32 @@
#include "../defines.hpp"
#include "wayland.hpp"
#include "../helpers/Math.hpp"
#include "LockSurface.hpp"
#include <memory>
class COutput {
public:
COutput(SP<CCWlOutput> output, uint32_t name);
COutput() = default;
~COutput() = default;
uint32_t name = 0;
bool focused = false;
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 = "";
void create(WP<COutput> pSelf, SP<CCWlOutput> pWlOutput, uint32_t name);
std::unique_ptr<CSessionLockSurface> sessionLockSurface;
OUTPUTID m_ID = 0;
bool focused = false;
bool done = false;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
Vector2D size;
int scale = 1;
std::string stringName = "";
std::string stringPort = "";
std::string stringDesc = "";
SP<CCWlOutput> output = nullptr;
UP<CSessionLockSurface> m_sessionLockSurface;
private:
SP<CCWlOutput> m_wlOutput = nullptr;
WP<COutput> m_self;
void createSessionLockSurface();
Vector2D getViewport() const;
};

View file

@ -26,7 +26,13 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
if (caps & WL_SEAT_CAPABILITY_POINTER) {
m_pPointer = makeShared<CCWlPointer>(r->sendGetPointer());
static const auto HIDECURSOR = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
if (!*HIDECURSOR)
g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation);
if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds)
return;
@ -40,16 +46,34 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
if (!m_pCursorShape)
return;
static const auto HIDE = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
m_pCursorShape->lastCursorSerial = serial;
if (*HIDE)
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);
});
}
@ -131,7 +155,7 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
}
void CSeatManager::registerCursorShape(SP<CCWpCursorShapeManagerV1> shape) {
m_pCursorShape = std::make_unique<CCursorShape>(shape);
m_pCursorShape = makeUnique<CCursorShape>(shape);
}
bool CSeatManager::registered() {

View file

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

View file

@ -6,6 +6,7 @@
#include "../auth/Auth.hpp"
#include "../auth/Fingerprint.hpp"
#include "Egl.hpp"
#include <hyprutils/memory/UniquePtr.hpp>
#include <sys/wait.h>
#include <sys/poll.h>
#include <sys/mman.h>
@ -38,12 +39,9 @@ CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const b
setMallocThreshold();
m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str());
if (!m_sWaylandState.display) {
Debug::log(CRIT, "Couldn't connect to a wayland compositor");
exit(1);
}
RASSERT(m_sWaylandState.display, "Couldn't connect to a wayland compositor");
g_pEGL = std::make_unique<CEGL>(m_sWaylandState.display);
g_pEGL = makeUnique<CEGL>(m_sWaylandState.display);
if (!immediate) {
static const auto GRACE = g_pConfigManager->getValue<Hyprlang::INT>("general:grace");
@ -75,7 +73,7 @@ static void registerSignalAction(int sig, void (*handler)(int), int sa_flags = 0
static void handleUnlockSignal(int sig) {
if (sig == SIGUSR1) {
Debug::log(LOG, "Unlocking with a SIGUSR1");
g_pHyprlock->releaseSessionLock();
g_pAuth->enqueueUnlock();
}
}
@ -94,20 +92,6 @@ static void handlePollTerminate(int sig) {
;
}
static void handleCriticalSignal(int sig) {
g_pHyprlock->attemptRestoreOnDeath();
// remove our handlers
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGABRT, &sa, nullptr);
sigaction(SIGSEGV, &sa, nullptr);
abort();
}
static char* gbm_find_render_node(drmDevice* device) {
drmDevice* devices[64];
char* render_node = nullptr;
@ -243,10 +227,7 @@ void CHyprlock::addDmabufListener() {
memcpy(&device, device_arr->data, sizeof(device));
drmDevice* drmDev;
if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) {
Debug::log(WARN, "[dmabuf] unable to open main device?");
exit(1);
}
RASSERT(drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) == 0, "unable to open main device?");
dma.gbmDevice = createGBMDevice(drmDev);
drmFreeDevice(&drmDev);
@ -283,10 +264,11 @@ void CHyprlock::run() {
} else if (IFACE == ext_session_lock_manager_v1_interface.name)
m_sWaylandState.sessionLock =
makeShared<CCExtSessionLockManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &ext_session_lock_manager_v1_interface, 1));
else if (IFACE == wl_output_interface.name)
m_vOutputs.emplace_back(
std::make_unique<COutput>(makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_output_interface, 4)), name));
else if (IFACE == wp_cursor_shape_manager_v1_interface.name)
else if (IFACE == wl_output_interface.name) {
const auto POUTPUT = makeShared<COutput>();
POUTPUT->create(POUTPUT, makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wl_output_interface, 4)), name);
m_vOutputs.emplace_back(POUTPUT);
} else if (IFACE == wp_cursor_shape_manager_v1_interface.name)
g_pSeatManager->registerCursorShape(
makeShared<CCWpCursorShapeManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &wp_cursor_shape_manager_v1_interface, 1)));
else if (IFACE == wl_compositor_interface.name)
@ -308,9 +290,9 @@ void CHyprlock::run() {
});
m_sWaylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t name) {
Debug::log(LOG, " | removed iface {}", name);
auto outputIt = std::ranges::find_if(m_vOutputs, [name](const auto& other) { return other->name == name; });
auto outputIt = std::ranges::find_if(m_vOutputs, [id = name](const auto& other) { return other->m_ID == id; });
if (outputIt != m_vOutputs.end()) {
g_pRenderer->removeWidgetsFor(outputIt->get()->sessionLockSurface.get());
g_pRenderer->removeWidgetsFor((*outputIt)->m_ID);
m_vOutputs.erase(outputIt);
}
});
@ -325,8 +307,8 @@ void CHyprlock::run() {
// gather info about monitors
wl_display_roundtrip(m_sWaylandState.display);
g_pRenderer = std::make_unique<CRenderer>();
g_pAuth = std::make_unique<CAuth>();
g_pRenderer = makeUnique<CRenderer>();
g_pAuth = makeUnique<CAuth>();
g_pAuth->start();
Debug::log(LOG, "Running on {}", m_sCurrentDesktop);
@ -361,8 +343,6 @@ void CHyprlock::run() {
registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART);
registerSignalAction(SIGUSR2, handleForceUpdateSignal);
registerSignalAction(SIGRTMIN, handlePollTerminate);
registerSignalAction(SIGSEGV, handleCriticalSignal);
registerSignalAction(SIGABRT, handleCriticalSignal);
pollfd pollfds[2];
pollfds[0] = {
@ -386,24 +366,13 @@ void CHyprlock::run() {
events = poll(pollfds, fdcount, 5000);
if (events < 0) {
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
wl_display_cancel_read(m_sWaylandState.display);
if (errno == EINTR)
continue;
Debug::log(CRIT, "[core] Polling fds failed with {}", errno);
attemptRestoreOnDeath();
m_bTerminate = true;
exit(1);
continue;
}
for (size_t i = 0; i < fdcount; ++i) {
if (pollfds[i].revents & POLLHUP) {
Debug::log(CRIT, "[core] Disconnected from pollfd id {}", i);
attemptRestoreOnDeath();
m_bTerminate = true;
exit(1);
}
RASSERT(!(pollfds[i].revents & POLLHUP), "[core] Disconnected from pollfd id {}", i);
}
wl_display_read_events(m_sWaylandState.display);
@ -554,23 +523,23 @@ void CHyprlock::clearPasswordBuffer() {
void CHyprlock::renderOutput(const std::string& stringPort) {
const auto MON = std::ranges::find_if(m_vOutputs, [stringPort](const auto& other) { return other->stringPort == stringPort; });
if (MON == m_vOutputs.end() || !MON->get())
if (MON == m_vOutputs.end() || !*MON)
return;
const auto PMONITOR = MON->get();
const auto& PMONITOR = *MON;
if (!PMONITOR->sessionLockSurface)
if (!PMONITOR->m_sessionLockSurface)
return;
PMONITOR->sessionLockSurface->render();
PMONITOR->m_sessionLockSurface->render();
}
void CHyprlock::renderAllOutputs() {
for (auto& o : m_vOutputs) {
if (!o->sessionLockSurface)
if (!o->m_sessionLockSurface)
continue;
o->sessionLockSurface->render();
o->m_sessionLockSurface->render();
}
}
@ -634,6 +603,9 @@ void CHyprlock::onKey(uint32_t key, bool down) {
return;
}
if (g_pAuth->m_bDisplayFailText)
g_pAuth->resetDisplayFail();
if (down) {
m_bCapsLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED);
m_bNumLock = xkb_state_mod_name_is_active(g_pSeatManager->m_pXKBState, XKB_MOD_NAME_NUM, XKB_STATE_MODS_LOCKED);
@ -697,6 +669,61 @@ void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
}
}
void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (!m_focusedOutput.lock())
return;
// TODO: add the UNLIKELY marco from Hyprland
if (!m_focusedOutput->m_sessionLockSurface)
return;
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
if (widget->containsPoint(SCALEDPOS))
widget->onClick(button, down, pos);
}
}
void CHyprlock::onHover(const Vector2D& pos) {
if (!m_focusedOutput.lock())
return;
if (!m_focusedOutput->m_sessionLockSurface)
return;
bool outputNeedsRedraw = false;
bool cursorChanged = false;
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
const bool CONTAINSPOINT = widget->containsPoint(SCALEDPOS);
const bool HOVERED = widget->isHovered();
if (CONTAINSPOINT) {
if (!HOVERED) {
widget->setHover(true);
widget->onHover(pos);
outputNeedsRedraw = true;
}
if (!cursorChanged)
cursorChanged = true;
} else if (HOVERED) {
widget->setHover(false);
outputNeedsRedraw = true;
}
}
if (!cursorChanged)
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
if (outputNeedsRedraw)
m_focusedOutput->m_sessionLockSurface->render();
}
bool CHyprlock::acquireSessionLock() {
Debug::log(LOG, "Locking session");
m_sLockState.lock = makeShared<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());
@ -723,7 +750,7 @@ bool CHyprlock::acquireSessionLock() {
if (!o->done)
continue;
o->sessionLockSurface = std::make_unique<CSessionLockSurface>(o.get());
o->createSessionLockSurface();
}
return true;
@ -845,19 +872,6 @@ void CHyprlock::enqueueForceUpdateTimers() {
nullptr, false);
}
std::string CHyprlock::spawnSync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
Debug::log(ERR, "Failed to run \"{}\"", cmd);
return "";
}
if (!proc.stdErr().empty())
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());
return proc.stdOut();
}
SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
return m_sWaylandState.screencopy;
}
@ -865,53 +879,3 @@ SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
SP<CCWlShm> CHyprlock::getShm() {
return m_sWaylandState.shm;
}
void CHyprlock::attemptRestoreOnDeath() {
if (m_bTerminate || m_sCurrentDesktop != "Hyprland")
return;
const auto XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!XDG_RUNTIME_DIR || !HIS)
return;
// dirty hack
uint64_t timeNowMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(0)).count();
const auto LASTRESTARTPATH = std::string{XDG_RUNTIME_DIR} + "/.hyprlockrestart";
if (std::filesystem::exists(LASTRESTARTPATH)) {
std::ifstream ifs(LASTRESTARTPATH);
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
uint64_t timeEncoded = 0;
try {
timeEncoded = std::stoull(content);
} catch (std::exception& e) {
// oops?
ifs.close();
std::filesystem::remove(LASTRESTARTPATH);
return;
}
ifs.close();
if (timeNowMs - timeEncoded < 4000 /* 4s, seems reasonable */) {
Debug::log(LOG, "Not restoring on death; less than 4s since last death");
return;
}
}
std::ofstream ofs(LASTRESTARTPATH, std::ios::trunc);
ofs << timeNowMs;
ofs.close();
if (m_bLocked && m_sLockState.lock) {
m_sLockState.lock.reset();
// Destroy sessionLockSurfaces
m_vOutputs.clear();
}
spawnSync("hyprctl keyword misc:allow_session_lock_restore true");
spawnSync("hyprctl dispatch exec \"hyprlock --immediate --immediate-render\"");
}

View file

@ -37,9 +37,6 @@ class CHyprlock {
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);
@ -51,11 +48,9 @@ class CHyprlock {
bool acquireSessionLock();
void releaseSessionLock();
void attemptRestoreOnDeath();
std::string spawnSync(const std::string& cmd);
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);
@ -100,10 +95,13 @@ class CHyprlock {
//
std::chrono::system_clock::time_point m_tGraceEnds;
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 {
@ -167,4 +165,4 @@ class CHyprlock {
std::vector<uint32_t> m_vPressedKeys;
};
inline std::unique_ptr<CHyprlock> g_pHyprlock;
inline UP<CHyprlock> g_pHyprlock;

View file

@ -1,8 +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 WP CWeakPointer
#define UP CUniquePointer
typedef int64_t OUTPUTID;
constexpr OUTPUTID OUTPUT_INVALID = -1;

View file

@ -5,9 +5,11 @@
#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);
@ -137,3 +139,22 @@ int createPoolFile(size_t size, std::string& name) {
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

@ -7,3 +7,5 @@
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

@ -29,6 +29,14 @@ std::optional<std::string> parseArg(const std::vector<std::string>& args, const
}
}
static void printVersion() {
constexpr bool ISTAGGEDRELEASE = std::string_view(HYPRLOCK_COMMIT) == HYPRLOCK_VERSION_COMMIT;
if (ISTAGGEDRELEASE)
std::println("Hyprlock version v{}", HYPRLOCK_VERSION);
else
std::println("Hyprlock version v{} (commit {})", HYPRLOCK_VERSION, HYPRLOCK_COMMIT);
}
int main(int argc, char** argv, char** envp) {
std::string configPath;
std::string wlDisplay;
@ -47,12 +55,7 @@ int main(int argc, char** argv, char** envp) {
}
if (arg == "--version" || arg == "-V") {
constexpr bool ISTAGGEDRELEASE = std::string_view(HYPRLOCK_COMMIT) == HYPRLOCK_VERSION_COMMIT;
if (ISTAGGEDRELEASE)
std::println("Hyprlock version v{}", HYPRLOCK_VERSION);
else
std::println("Hyprlock version v{} (commit {})", HYPRLOCK_VERSION, HYPRLOCK_COMMIT);
printVersion();
return 0;
}
@ -90,10 +93,11 @@ int main(int argc, char** argv, char** envp) {
}
}
g_pAnimationManager = std::make_unique<CHyprlockAnimationManager>();
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());
@ -103,11 +107,11 @@ int main(int argc, char** argv, char** envp) {
return 1;
}
if (noFadeIn)
if (noFadeIn || immediate)
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
try {
g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate, immediateRender);
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,7 +2,6 @@
#include "../config/ConfigManager.hpp"
#include "../core/Egl.hpp"
#include <cairo/cairo.h>
#include <magic.h>
#include <pango/pangocairo.h>
#include <algorithm>
#include <filesystem>
@ -48,15 +47,12 @@ void CAsyncResourceGatherer::enqueueScreencopyFrames() {
}
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();
scframes.emplace_back(std::make_unique<CScreencopyFrame>(PMONITOR));
scframes.emplace_back(makeUnique<CScreencopyFrame>(*MON));
}
}
@ -221,7 +217,7 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;
static const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
std::string text = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset;
std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset;
if (*TRIM) {
text.erase(0, text.find_first_not_of(" \n\r\t"));
@ -304,17 +300,6 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
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) {
@ -349,7 +334,7 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() {
// plant timer for callback
if (r.callback)
g_pHyprlock->addTimer(std::chrono::milliseconds(0), timerCallback, new STimerCallbackData{.cb = r.callback, .data = r.callbackData});
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [cb = r.callback](auto, auto) { cb(); }, nullptr);
}
}
}

View file

@ -37,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);
@ -75,7 +74,7 @@ class CAsyncResourceGatherer {
Vector2D size;
};
std::vector<std::unique_ptr<CScreencopyFrame>> scframes;
std::vector<UP<CScreencopyFrame>> scframes;
std::vector<SPreloadTarget> preloadTargets;
std::mutex preloadTargetsMutex;

View file

@ -117,6 +117,6 @@ CFramebuffer::~CFramebuffer() {
release();
}
bool CFramebuffer::isAllocated() {
bool CFramebuffer::isAllocated() const {
return m_iFb != (GLuint)-1;
}
}

View file

@ -13,7 +13,7 @@ class CFramebuffer {
void bind() const;
void release();
void reset();
bool isAllocated();
bool isAllocated() const;
Vector2D m_vSize;
@ -21,4 +21,4 @@ class CFramebuffer {
GLuint m_iFb = -1;
CTexture* m_pStencilTex = nullptr;
};
};

View file

@ -194,15 +194,13 @@ CRenderer::CRenderer() {
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"));
}
//
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
static const auto DISABLEBAR = g_pConfigManager->getValue<Hyprlang::INT>("general:disable_loading_bar");
projection = Mat3x3::outputProjection(surf.size, HYPRUTILS_TRANSFORM_NORMAL);
g_pEGL->makeCurrent(surf.eglSurface);
@ -221,18 +219,10 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
SRenderFeedback feedback;
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
if (WAITFORASSETS) {
// render status
if (!*DISABLEBAR) {
CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2};
renderRect(progress, CHyprColor{0.2f, 0.1f, 0.1f, 1.f}, 0);
}
} else {
if (!WAITFORASSETS) {
// render widgets
const auto WIDGETS = getOrCreateWidgetsFor(&surf);
for (auto& w : *WIDGETS) {
const auto WIDGETS = getOrCreateWidgetsFor(surf);
for (auto& w : WIDGETS) {
feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame;
}
}
@ -282,7 +272,7 @@ void CRenderer::renderBorder(const CBox& box, const CGradientValueData& gradient
glUniformMatrix3fv(borderShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform4fv(borderShader.gradient, gradient.m_vColorsOkLabA.size(), (float*)gradient.m_vColorsOkLabA.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);
@ -397,62 +387,49 @@ void CRenderer::renderTextureMix(const CBox& box, const CTexture& tex, const CTe
glBindTexture(tex.m_iTarget, 0);
}
std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) {
if (!widgets.contains(surf)) {
template <class Widget>
static void createWidget(std::vector<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::ranges::sort(CWIDGETS, [](CConfigManager::SWidgetConfig& a, CConfigManager::SWidgetConfig& b) {
return std::any_cast<Hyprlang::INT>(a.values.at("zindex")) < std::any_cast<Hyprlang::INT>(b.values.at("zindex"));
});
const auto POUTPUT = surf.m_outputRef.lock();
for (auto& c : CWIDGETS) {
if (!c.monitor.empty() && c.monitor != surf->output->stringPort && !surf->output->stringDesc.starts_with(c.monitor) &&
!surf->output->stringDesc.starts_with("desc:" + c.monitor))
if (!c.monitor.empty() && c.monitor != POUTPUT->stringPort && !POUTPUT->stringDesc.starts_with(c.monitor) && !("desc:" + POUTPUT->stringDesc).starts_with(c.monitor))
continue;
// by type
if (c.type == "background") {
const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
std::string resourceID = "";
if (PATH == "screenshot") {
resourceID = CScreencopyFrame::getResourceId(surf->output);
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
if (asyncResourceGatherer->gathered) {
if (!asyncResourceGatherer->getAssetByID(resourceID))
resourceID = ""; // Fallback to solid color (background:color)
}
if (!g_pHyprlock->getScreencopy()) {
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
resourceID = "";
}
} else if (!PATH.empty())
resourceID = "background:" + PATH;
widgets[surf].emplace_back(std::make_unique<CBackground>(surf->size, surf->output, resourceID, c.values, PATH == "screenshot"));
createWidget<CBackground>(widgets[surf.m_outputID]);
} else if (c.type == "input-field") {
widgets[surf].emplace_back(std::make_unique<CPasswordInputField>(surf->size, c.values, surf->output->stringPort));
createWidget<CPasswordInputField>(widgets[surf.m_outputID]);
} else if (c.type == "label") {
widgets[surf].emplace_back(std::make_unique<CLabel>(surf->size, c.values, surf->output->stringPort));
createWidget<CLabel>(widgets[surf.m_outputID]);
} else if (c.type == "shape") {
widgets[surf].emplace_back(std::make_unique<CShape>(surf->size, c.values));
createWidget<CShape>(widgets[surf.m_outputID]);
} else if (c.type == "image") {
const std::string PATH = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
std::string resourceID = "";
if (!PATH.empty())
resourceID = "image:" + PATH;
widgets[surf].emplace_back(std::make_unique<CImage>(surf->size, surf->output, resourceID, c.values));
createWidget<CImage>(widgets[surf.m_outputID]);
} else {
Debug::log(ERR, "Unknown widget type: {}", c.type);
continue;
}
widgets[surf.m_outputID].back()->configure(c.values, POUTPUT);
}
}
return &widgets[surf];
return widgets[surf.m_outputID];
}
void CRenderer::blurFB(const CFramebuffer& outfb, SBlurParams params) {
@ -618,8 +595,15 @@ 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() {

View file

@ -1,9 +1,9 @@
#pragma once
#include <memory>
#include <chrono>
#include <optional>
#include "Shader.hpp"
#include "../defines.hpp"
#include "../core/LockSurface.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/Color.hpp"
@ -12,7 +12,7 @@
#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:
@ -29,7 +29,7 @@ class CRenderer {
float boostA = 1.0;
};
SRenderFeedback renderLock(const CSessionLockSurface& surface);
SRenderFeedback renderLock(const CSessionLockSurface& surf);
void renderRect(const CBox& box, const CHyprColor& col, int rounding = 0);
void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0);
@ -37,37 +37,37 @@ class CRenderer {
void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional<eTransform> tr = {});
void blurFB(const CFramebuffer& outfb, SBlurParams params);
std::unique_ptr<CAsyncResourceGatherer> asyncResourceGatherer;
std::chrono::system_clock::time_point firstFullFrameTime;
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);
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 texMixShader;
CShader blurShader1;
CShader blurShader2;
CShader blurPrepareShader;
CShader blurFinishShader;
CShader borderShader;
Mat3x3 projMatrix = Mat3x3::identity();
Mat3x3 projection;
Mat3x3 projMatrix = Mat3x3::identity();
Mat3x3 projection;
PHLANIMVAR<float> opacity;
PHLANIMVAR<float> opacity;
std::vector<GLint> boundFBs;
std::vector<GLint> boundFBs;
};
inline std::unique_ptr<CRenderer> g_pRenderer;
inline UP<CRenderer> g_pRenderer;

View file

@ -11,6 +11,7 @@
#include <array>
#include <cstdint>
#include <gbm.h>
#include <hyprutils/memory/UniquePtr.hpp>
#include <unistd.h>
#include <sys/mman.h>
#include <libdrm/drm_fourcc.h>
@ -22,24 +23,27 @@ static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullpt
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr;
//
std::string CScreencopyFrame::getResourceId(COutput* output) {
return std::format("screencopy:{}-{}x{}", output->stringPort, output->size.x, output->size.y);
std::string CScreencopyFrame::getResourceId(SP<COutput> pOutput) {
return std::format("screencopy:{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y);
}
CScreencopyFrame::CScreencopyFrame(COutput* output) : m_output(output) {
m_resourceID = getResourceId(m_output);
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 = std::make_unique<CSCSHMFrame>(m_sc);
m_frame = makeUnique<CSCSHMFrame>(m_sc);
else
m_frame = std::make_unique<CSCDMAFrame>(m_sc);
m_frame = makeUnique<CSCDMAFrame>(m_sc);
}
void CScreencopyFrame::captureOutput() {
m_sc = makeShared<CCZwlrScreencopyFrameV1>(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, m_output->output->resource()));
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);

View file

@ -22,9 +22,9 @@ class ISCFrame {
class CScreencopyFrame {
public:
static std::string getResourceId(COutput* output);
static std::string getResourceId(SP<COutput> pOutput);
CScreencopyFrame(COutput* mon);
CScreencopyFrame(SP<COutput> pOutput);
~CScreencopyFrame() = default;
void captureOutput();
@ -35,10 +35,10 @@ class CScreencopyFrame {
SPreloadedAsset m_asset;
private:
COutput* m_output = nullptr;
std::unique_ptr<ISCFrame> m_frame = nullptr;
WP<COutput> m_outputRef;
UP<ISCFrame> m_frame = nullptr;
bool m_dmaFailed = false;
bool m_dmaFailed = false;
};
// Uses a gpu buffer created via gbm_bo

View file

@ -10,23 +10,15 @@
#include <GLES3/gl32.h>
CBackground::~CBackground() {
if (reloadTimer) {
reloadTimer->cancel();
reloadTimer.reset();
}
if (fade) {
if (fade->crossFadeTimer) {
fade->crossFadeTimer->cancel();
fade->crossFadeTimer.reset();
}
fade.reset();
}
reset();
}
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) {
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"));
@ -48,6 +40,29 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std:
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, ""));
@ -57,30 +72,41 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std:
}
}
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(std::shared_ptr<CTimer> self, void* data) {
const auto PBG = (CBackground*)data;
PBG->onReloadTimerUpdate();
PBG->plantReloadTimer();
static void onReloadTimer(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG) {
PBG->onReloadTimerUpdate();
PBG->plantReloadTimer();
}
}
static void onCrossFadeTimer(std::shared_ptr<CTimer> self, void* data) {
const auto PBG = (CBackground*)data;
PBG->onCrossFadeTimerUpdate();
static void onCrossFadeTimer(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG)
PBG->onCrossFadeTimerUpdate();
}
static void onAssetCallback(void* data) {
const auto PBG = (CBackground*)data;
PBG->startCrossFadeOrUpdateRender();
}
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
onAssetCallback(data);
static void onAssetCallback(WP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG)
PBG->startCrossFadeOrUpdateRender();
}
bool CBackground::draw(const SRenderData& data) {
@ -115,7 +141,7 @@ bool CBackground::draw(const SRenderData& data) {
// 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;
}
@ -142,16 +168,19 @@ bool CBackground::draw(const SRenderData& data) {
if (fade)
g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0,
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossFadeTime), 0,
HYPRUTILS_TRANSFORM_NORMAL);
transform);
else
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0,
isScreenshot ?
wlTransformToHyprutils(invertTransform(output->transform)) :
HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, transform);
if (blurPasses > 0)
g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{
.size = blurSize, .passes = blurPasses, .noise = noise, .contrast = contrast, .brightness = brightness, .vibrancy = vibrancy, .vibrancy_darkness = 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);
}
@ -179,9 +208,9 @@ bool CBackground::draw(const SRenderData& data) {
void CBackground::plantReloadTimer() {
if (reloadTime == 0)
reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onReloadTimer, this, true);
reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
else if (reloadTime > 0)
reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onReloadTimer, this, false);
reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
}
void CBackground::onCrossFadeTimerUpdate() {
@ -190,7 +219,7 @@ void CBackground::onCrossFadeTimerUpdate() {
if (fade) {
fade->crossFadeTimer.reset();
fade.reset(nullptr);
fade.reset();
}
if (blurPasses <= 0 && !isScreenshot)
@ -202,7 +231,7 @@ void CBackground::onCrossFadeTimerUpdate() {
pendingAsset = nullptr;
firstRender = true;
g_pHyprlock->renderOutput(output->stringPort);
g_pHyprlock->renderOutput(outputPort);
}
void CBackground::onReloadTimerUpdate() {
@ -211,7 +240,7 @@ void CBackground::onReloadTimerUpdate() {
// Path parsing and early returns
if (!reloadCommand.empty()) {
path = g_pHyprlock->spawnSync(reloadCommand);
path = spawnSync(reloadCommand);
if (path.ends_with('\n'))
path.pop_back();
@ -245,8 +274,7 @@ void CBackground::onReloadTimerUpdate() {
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);
}
@ -262,7 +290,7 @@ void CBackground::startCrossFadeOrUpdateRender() {
if (crossFadeTime > 0) {
// Start a fade
if (!fade)
fade = std::make_unique<SFade>(std::chrono::system_clock::now(), 0, nullptr);
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) {
@ -270,17 +298,18 @@ void CBackground::startCrossFadeOrUpdateRender() {
fade->crossFadeTimer.reset();
}
}
fade->start = std::chrono::system_clock::now();
fade->a = 0;
fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossFadeTime)), onCrossFadeTimer, this);
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), onAssetCallbackTimer, this);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
}
g_pHyprlock->renderOutput(output->stringPort);
g_pHyprlock->renderOutput(outputPort);
}

View file

@ -6,6 +6,7 @@
#include "../../core/Timer.hpp"
#include "../Framebuffer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include <hyprutils/math/Misc.hpp>
#include <string>
#include <unordered_map>
#include <any>
@ -23,10 +24,16 @@ struct SFade {
class CBackground : public IWidget {
public:
CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props, bool ss_);
CBackground() = default;
~CBackground();
void registerSelf(const SP<CBackground>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
void reset(); // Unload assets, remove timers, etc.
void renderRect(CHyprColor color);
void onReloadTimerUpdate();
@ -35,6 +42,8 @@ class CBackground : public IWidget {
void startCrossFadeOrUpdateRender();
private:
WP<CBackground> m_self;
// if needed
CFramebuffer blurredFB;
@ -48,6 +57,9 @@ class CBackground : public IWidget {
Vector2D viewport;
std::string path = "";
std::string outputPort;
Hyprutils::Math::eTransform transform;
std::string resourceID;
std::string pendingResourceID;
@ -55,16 +67,15 @@ class CBackground : public IWidget {
CHyprColor color;
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
bool isScreenshot = false;
SPreloadedAsset* pendingAsset = nullptr;
bool firstRender = true;
std::unique_ptr<SFade> fade;
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

@ -22,7 +22,7 @@ namespace std {
}
#endif
Vector2D rotateVector(const Vector2D& vec, const double& ang) {
static Vector2D rotateVector(const Vector2D& vec, const double& ang) {
const double COS = std::abs(std::cos(ang));
const double SIN = std::abs(std::sin(ang));
return Vector2D((vec.x * COS) + (vec.y * SIN), (vec.x * SIN) + (vec.y * COS));
@ -100,22 +100,31 @@ static void replaceAllAttempts(std::string& str) {
}
static void replaceAllLayout(std::string& str) {
std::string layoutName = "error";
const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout;
const auto LAYOUTIDX = g_pHyprlock->m_uiActiveLayout;
const auto LAYOUTNAME = xkb_keymap_layout_get_name(g_pSeatManager->m_pXKBKeymap, LAYOUTIDX);
const std::string STR = LAYOUTNAME ? LAYOUTNAME : "error";
size_t pos = 0;
if (g_pSeatManager->m_pXKBKeymap) {
const auto PNAME = xkb_keymap_layout_get_name(g_pSeatManager->m_pXKBKeymap, LAYOUTIDX);
if (PNAME)
layoutName = PNAME;
}
size_t pos = 0;
while ((pos = str.find("$LAYOUT", pos)) != std::string::npos) {
if (str.substr(pos, 8).ends_with('[') && str.substr(pos).contains(']')) {
const std::string REPL = str.substr(pos + 8, str.find_first_of(']', pos) - 8 - pos);
const CVarList LANGS(REPL);
const std::string LANG = LANGS[LAYOUTIDX].empty() ? STR : LANGS[LAYOUTIDX] == "!" ? "" : LANGS[LAYOUTIDX];
if (LAYOUTIDX >= LANGS.size()) {
Debug::log(ERR, "Layout index {} out of bounds. Max is {}.", LAYOUTIDX, LANGS.size() - 1);
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();
}
}
}
@ -257,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,22 +1,36 @@
#pragma once
#include "../../defines.hpp"
#include "../../helpers/Math.hpp"
#include <string>
#include <unordered_map>
#include <any>
class COutput;
class IWidget {
public:
struct SRenderData {
float opacity = 1;
};
virtual ~IWidget() = default;
virtual 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;
static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign,
const double& ang = 0);
static int roundingForBox(const CBox& box, int roundingConfig);
static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness);
virtual CBox getBoundingBoxWl() const {
return CBox();
};
virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {}
virtual void onHover(const Vector2D& pos) {}
bool containsPoint(const Vector2D& pos) const;
struct SFormatResult {
std::string formatted;
float updateEveryMs = 0; // 0 means don't (static)
@ -26,4 +40,10 @@ class IWidget {
};
static SFormatResult formatString(std::string in);
void setHover(bool hover);
bool isHovered() const;
private:
bool hovered = false;
};

View file

@ -6,35 +6,33 @@
#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->renderUpdate();
}
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
onAssetCallback(data);
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();
@ -65,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);
}
@ -75,34 +71,41 @@ void CImage::onTimerUpdate() {
void CImage::plantTimer() {
if (reloadTime == 0) {
imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true);
imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, true);
} else if (reloadTime > 0)
imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onTimer, this, false);
imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onTimer(REF); }, nullptr, false);
}
CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map<std::string, std::any>& props) :
viewport(viewport_), resourceID(resourceID_), output(output_), shadow(this, props, viewport_) {
void CImage::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
viewport = pOutput->getViewport();
stringPort = pOutput->stringPort;
shadow.configure(m_self.lock(), props, viewport);
try {
size = std::any_cast<Hyprlang::INT>(props.at("size"));
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = *CGradientValueData::fromAnyPv(props.at("border_color"));
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
size = std::any_cast<Hyprlang::INT>(props.at("size"));
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = *CGradientValueData::fromAnyPv(props.at("border_color"));
configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
path = std::any_cast<Hyprlang::STRING>(props.at("path"));
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
path = std::any_cast<Hyprlang::STRING>(props.at("path"));
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CImage: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing propperty for CImage: {}", e.what()); //
}
angle = angle * M_PI / 180.0;
resourceID = "image:" + path;
angle = angle * M_PI / 180.0;
if (reloadTime > -1) {
try {
@ -113,6 +116,25 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r
}
}
void CImage::reset() {
if (imageTimer) {
imageTimer->cancel();
imageTimer.reset();
}
if (g_pHyprlock->m_bTerminate)
return;
imageFB.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 (resourceID.empty())
@ -176,10 +198,10 @@ bool CImage::draw(const SRenderData& data) {
shadow.draw(data);
const auto TEXPOS = posFromHVAlign(viewport, tex->m_vSize, pos, halign, valign, angle);
pos = posFromHVAlign(viewport, tex->m_vSize, configPos, halign, valign, angle);
texbox.x = TEXPOS.x;
texbox.y = TEXPOS.y;
texbox.x = pos.x;
texbox.y = pos.y;
texbox.round();
texbox.rot = angle;
@ -204,9 +226,32 @@ void CImage::renderUpdate() {
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), onAssetCallbackTimer, this);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
}
g_pHyprlock->renderOutput(output->stringPort);
g_pHyprlock->renderOutput(stringPort);
}
CBox CImage::getBoundingBoxWl() const {
if (!imageFB.isAllocated())
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - imageFB.m_cTex.m_vSize.y},
imageFB.m_cTex.m_vSize,
};
}
void CImage::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CImage::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}

View file

@ -17,16 +17,26 @@ class COutput;
class CImage : public IWidget {
public:
CImage(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map<std::string, std::any>& props);
CImage() = default;
~CImage();
void registerSelf(const SP<CImage>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
void renderUpdate();
void onTimerUpdate();
void plantTimer();
private:
WP<CImage> m_self;
CFramebuffer imageFB;
int size;
@ -35,6 +45,7 @@ class CImage : public IWidget {
double angle;
CGradientValueData color;
Vector2D pos;
Vector2D configPos;
std::string halign, valign, path;
@ -42,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

@ -3,36 +3,31 @@
#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) {
if (data == nullptr)
return;
const auto PLABEL = (CLabel*)data;
// update label
PLABEL->onTimerUpdate();
// plant new timer
PLABEL->plantTimer();
}
static void onAssetCallback(void* data) {
const auto PLABEL = (CLabel*)data;
PLABEL->renderUpdate();
}
static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
onAssetCallback(data);
static void onAssetCallback(WP<CLabel> ref) {
if (auto PLABEL = ref.lock(); PLABEL)
PLABEL->renderUpdate();
}
std::string CLabel::getUniqueResourceId() {
@ -57,32 +52,39 @@ void CLabel::onTimerUpdate() {
pendingResourceID = request.id;
request.asset = label.formatted;
request.callback = onAssetCallback;
request.callbackData = this;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CLabel::plantTimer() {
if (label.updateEveryMs != 0)
labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), onTimer, this, label.allowForceUpdate);
labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), [REF = m_self](auto, auto) { onTimer(REF); }, this, label.allowForceUpdate);
else if (label.updateEveryMs == 0 && label.allowForceUpdate)
labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true);
labelTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onTimer(REF); }, this, true);
}
CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) :
outputStringPort(output), shadow(this, props, viewport_) {
void CLabel::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
outputStringPort = pOutput->stringPort;
viewport = pOutput->getViewport();
shadow.configure(m_self.lock(), props, viewport);
try {
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
labelPreFormat = std::any_cast<Hyprlang::STRING>(props.at("text"));
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
angle = angle * M_PI / 180.0;
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
std::string textAlign = std::any_cast<Hyprlang::STRING>(props.at("text_align"));
std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
CHyprColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
CHyprColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
int fontSize = std::any_cast<Hyprlang::INT>(props.at("font_size"));
label = formatString(labelPreFormat);
@ -105,14 +107,30 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string,
RASSERT(false, "Missing property for CLabel: {}", e.what()); //
}
configPos = pos;
viewport = viewport_;
pos = configPos; // Label size not known yet
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
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);
@ -150,9 +168,29 @@ void CLabel::renderUpdate() {
} else {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
return;
}
g_pHyprlock->renderOutput(outputStringPort);
}
CBox CLabel::getBoundingBoxWl() const {
if (!asset)
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - asset->texture.m_vSize.y},
asset->texture.m_vSize,
};
}
void CLabel::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (down && !onclickCommand.empty())
spawnAsync(onclickCommand);
}
void CLabel::onHover(const Vector2D& pos) {
if (!onclickCommand.empty())
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}

View file

@ -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();
void registerSelf(const SP<CLabel>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
void reset();
void renderUpdate();
void onTimerUpdate();
void plantTimer();
private:
WP<CLabel> m_self;
std::string getUniqueResourceId();
std::string labelPreFormat;
@ -36,6 +46,7 @@ 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;

View file

@ -15,11 +15,25 @@
using namespace Hyprutils::String;
CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props, const std::string& output) :
viewport(viewport_), outputStringPort(output), shadow(this, props, viewport_) {
CPasswordInputField::~CPasswordInputField() {
reset();
}
void CPasswordInputField::registerSelf(const SP<CPasswordInputField>& self) {
m_self = self;
}
void CPasswordInputField::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
reset();
outputStringPort = pOutput->stringPort;
viewport = pOutput->getViewport();
shadow.configure(m_self.lock(), props, viewport);
try {
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
configSize = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport_);
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"));
@ -34,7 +48,6 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
configPlaceholderText = std::any_cast<Hyprlang::STRING>(props.at("placeholder_text"));
configFailText = std::any_cast<Hyprlang::STRING>(props.at("fail_text"));
configFailTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fail_timeout"));
fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
colorConfig.outer = CGradientValueData::fromAnyPv(props.at("outer_color"));
colorConfig.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
@ -46,6 +59,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
colorConfig.num = CGradientValueData::fromAnyPv(props.at("numlock_color"));
colorConfig.invertNum = std::any_cast<Hyprlang::INT>(props.at("invert_numlock"));
colorConfig.swapFont = std::any_cast<Hyprlang::INT>(props.at("swap_font_color"));
colorConfig.hiddenBase = std::any_cast<Hyprlang::INT>(props.at("hide_input_base_color"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CPasswordInputField: {}", e.what()); //
} catch (const std::out_of_range& e) {
@ -61,6 +75,16 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
g_pAnimationManager->createAnimation(0.f, fade.a, g_pConfigManager->m_AnimationTree.getConfig("inputFieldFade"));
g_pAnimationManager->createAnimation(0.f, dots.currentAmount, g_pConfigManager->m_AnimationTree.getConfig("inputFieldDots"));
g_pAnimationManager->createAnimation(configSize, size, g_pConfigManager->m_AnimationTree.getConfig("inputFieldWidth"));
g_pAnimationManager->createAnimation(colorConfig.inner, colorState.inner, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
g_pAnimationManager->createAnimation(*colorConfig.outer, colorState.outer, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
srand(std::chrono::system_clock::now().time_since_epoch().count());
pos = posFromHVAlign(viewport, size->goal(), configPos, halign, valign);
if (!dots.textFormat.empty()) {
dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat);
CAsyncResourceGatherer::SPreloadRequest request;
@ -74,23 +98,30 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
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());
// 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() {
@ -121,7 +152,8 @@ void CPasswordInputField::updateFade() {
*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);
} else if (INPUTUSED && fade.a->goal() != 1.0)
*fade.a = 1.0;
@ -133,6 +165,9 @@ void CPasswordInputField::updateDots() {
if (dots.currentAmount->goal() == passwordLength)
return;
if (checkWaiting)
return;
if (passwordLength == 0)
dots.currentAmount->setValueAndWarp(passwordLength);
else
@ -266,7 +301,7 @@ bool CPasswordInputField::draw(const SRenderData& data) {
}
}
if (passwordLength == 0 && !placeholder.resourceID.empty()) {
if (passwordLength == 0 && !checkWaiting && !placeholder.resourceID.empty()) {
SPreloadedAsset* currAsset = nullptr;
if (!placeholder.asset)
@ -291,13 +326,6 @@ bool CPasswordInputField::draw(const SRenderData& data) {
return redrawShadow || forceReload;
}
static void failTimeoutCallback(std::shared_ptr<CTimer> self, void* data) {
if (g_pAuth->m_bDisplayFailText) {
g_pAuth->m_bDisplayFailText = false;
g_pHyprlock->renderAllOutputs();
}
}
void CPasswordInputField::updatePlaceholder() {
if (passwordLength != 0) {
if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) {
@ -316,12 +344,7 @@ void CPasswordInputField::updatePlaceholder() {
placeholder.failedAttempts = g_pAuth->getFailedAttempts();
std::string newText;
if (displayFail) {
g_pHyprlock->addTimer(std::chrono::milliseconds(configFailTimeoutMs), failTimeoutCallback, nullptr);
newText = formatString(configFailText).formatted;
} else
newText = formatString(configPlaceholderText).formatted;
std::string newText = (displayFail) ? formatString(configFailText).formatted : formatString(configPlaceholderText).formatted;
// if the text is unchanged we don't need to do anything, unless we are swapping font color
const auto ALLOWCOLORSWAP = outThick == 0 && colorConfig.swapFont;
@ -353,6 +376,10 @@ void CPasswordInputField::updatePlaceholder() {
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);
}
@ -385,7 +412,7 @@ void CPasswordInputField::updateHiddenInputState() {
// randomize new thang
hiddenInputState.lastPasswordLength = passwordLength;
const auto BASEOK = colorConfig.outer->m_vColors.front().asOkLab();
const auto BASEOK = colorConfig.hiddenBase.asOkLab();
// convert to polar coordinates
const auto OKICHCHROMA = std::sqrt(std::pow(BASEOK.a, 2) + std::pow(BASEOK.b, 2));
@ -419,7 +446,7 @@ void CPasswordInputField::updateColors() {
if (checkWaiting)
targetGrad = colorConfig.check;
else if (displayFail)
else if (displayFail && passwordLength == 0)
targetGrad = colorConfig.fail;
CGradientValueData* outerTarget = colorConfig.outer;
@ -446,3 +473,14 @@ void CPasswordInputField::updateColors() {
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

@ -7,7 +7,7 @@
#include "Shadowable.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../helpers/AnimatedVariable.hpp"
#include <chrono>
#include <hyprutils/math/Vector2D.hpp>
#include <vector>
#include <any>
#include <unordered_map>
@ -16,37 +16,47 @@ 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 updateWidth();
void updateHiddenInputState();
void updateInputState();
void updateColors();
WP<CPasswordInputField> m_self;
bool firstRender = true;
bool redrawShadow = false;
bool checkWaiting = false;
bool displayFail = false;
void updateDots();
void updateFade();
void updatePlaceholder();
void updateWidth();
void updateHiddenInputState();
void updateInputState();
void updateColors();
size_t passwordLength = 0;
bool firstRender = true;
bool redrawShadow = false;
bool checkWaiting = false;
bool displayFail = false;
PHLANIMVAR<Vector2D> size;
Vector2D pos;
Vector2D viewport;
Vector2D configPos;
Vector2D configSize;
size_t passwordLength = 0;
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
uint64_t configFailTimeoutMs = 2000;
PHLANIMVAR<Vector2D> size;
Vector2D pos;
Vector2D viewport;
Vector2D configPos;
Vector2D configSize;
int outThick, rounding;
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
uint64_t configFailTimeoutMs = 2000;
int outThick, rounding;
struct {
PHLANIMVAR<float> currentAmount;
@ -74,7 +84,6 @@ class CPasswordInputField : public IWidget {
size_t failedAttempts = 0;
std::vector<std::string> registeredResourceIDs;
} placeholder;
struct {
@ -94,6 +103,8 @@ class CPasswordInputField : public IWidget {
CGradientValueData* num = nullptr;
CGradientValueData* both = nullptr;
CHyprColor hiddenBase;
int transitionMs = 0;
bool invertNum = false;
bool swapFont = false;

View file

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

View file

@ -11,22 +11,24 @@
class CShadowable {
public:
CShadowable(IWidget* widget_, const std::unordered_map<std::string, std::any>& props, const Vector2D& viewport_ /* TODO: make this not the entire viewport */);
virtual ~CShadowable() = default;
CShadowable() = default;
void configure(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;
CHyprColor 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;
CFramebuffer shadowFB;
};
};

View file

@ -1,30 +1,40 @@
#include "Shape.hpp"
#include "../Renderer.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include <cmath>
#include <hyprlang.hpp>
#include <sys/types.h>
CShape::CShape(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props) : shadow(this, props, viewport_) {
void CShape::registerSelf(const SP<CShape>& self) {
m_self = self;
}
void CShape::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
viewport = pOutput->getViewport();
shadow.configure(m_self.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"));
size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport);
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = std::any_cast<Hyprlang::INT>(props.at("color"));
borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color"));
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
angle = std::any_cast<Hyprlang::FLOAT>(props.at("rotate"));
xray = std::any_cast<Hyprlang::INT>(props.at("xray"));
onclickCommand = std::any_cast<Hyprlang::STRING>(props.at("onclick"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CShape: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CShape: {}", e.what()); //
}
viewport = viewport_;
angle = angle * M_PI / 180.0;
angle = angle * M_PI / 180.0;
const Vector2D VBORDER = {border, border};
const Vector2D REALSIZE = size + VBORDER * 2.0;
@ -94,3 +104,19 @@ bool CShape::draw(const SRenderData& data) {
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

@ -11,11 +11,20 @@
class CShape : public IWidget {
public:
CShape(const Vector2D& viewport, const std::unordered_map<std::string, std::any>& props);
CShape() = default;
virtual ~CShape() = default;
void registerSelf(const SP<CShape>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
private:
WP<CShape> m_self;
CFramebuffer shapeFB;
int rounding;
@ -32,6 +41,7 @@ class CShape : public IWidget {
std::string halign, valign;
bool firstRender = true;
std::string onclickCommand;
Vector2D viewport;
CShadowable shadow;