core: initial commit

This commit is contained in:
Vaxry 2024-02-18 23:08:03 +00:00
parent a2f0160ac6
commit a6ac79641a
41 changed files with 2834 additions and 1 deletions

65
.clang-format Normal file
View file

@ -0,0 +1,65 @@
---
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
ColumnLimit: 180
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks: Preserve
IndentCaseLabels: true
IndentWidth: 4
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Never
AllowShortEnumsOnASingleLine: false
BraceWrapping:
AfterEnum: false
AlignConsecutiveDeclarations: AcrossEmptyLines
NamespaceIndentation: All

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.vscode/
build/
protocols/

80
CMakeLists.txt Normal file
View file

@ -0,0 +1,80 @@
cmake_minimum_required(VERSION 3.19)
set(VERSION 0.1.0)
project(hyprlock
DESCRIPTION "A gpu-accelerated screen lock for Hyprland"
VERSION ${VERSION}
)
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprlock in Debug with CMake")
add_compile_definitions(HYPRLAND_DEBUG)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprlock in Release with CMake")
endif()
include_directories(
.
"protocols/"
)
# configure
set(CMAKE_CXX_STANDARD 23)
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value
-Wno-missing-field-initializers -Wno-narrowing)
# dependencies
message(STATUS "Checking deps...")
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon cairo pango pam)
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprlock ${SRCFILES})
target_link_libraries(hyprlock PRIVATE rt Threads::Threads PkgConfig::deps OpenGL::EGL OpenGL::GL)
# protocols
find_program(WaylandScanner NAMES wayland-scanner)
message(STATUS "Found WaylandScanner at ${WaylandScanner}")
execute_process(
COMMAND pkg-config --variable=pkgdatadir wayland-protocols
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE WAYLAND_PROTOCOLS_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
function(protocol protoPath protoName external)
if (external)
execute_process(
COMMAND ${WaylandScanner} client-header ${protoPath} protocols/${protoName}-protocol.h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
execute_process(
COMMAND ${WaylandScanner} private-code ${protoPath} protocols/${protoName}-protocol.c
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c)
else()
execute_process(
COMMAND ${WaylandScanner} client-header ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
execute_process(
COMMAND ${WaylandScanner} private-code ${WAYLAND_PROTOCOLS_DIR}/${protoPath} protocols/${protoName}-protocol.c
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprlock PRIVATE protocols/${protoName}-protocol.c)
endif()
endfunction()
make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so the dir won't be there
protocol("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock-v1" false)
protocol("staging/cursor-shape/cursor-shape-v1.xml" "cursor-shape-v1" false)
protocol("unstable/tablet/tablet-unstable-v2.xml" "tablet-unstable-v2" false)
protocol("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false)
protocol("stable/viewporter/viewporter.xml" "viewporter" false)
# Installation
install(TARGETS hyprlock)

View file

@ -1,2 +1,63 @@
# hyprlock # hyprlock
Hyprland's screen locking utility 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
## Example config
```ini
general {
disable_loading_bar = false
}
background {
monitor = DP-2
path = /home/me/someImage.png
}
background {
monitor = WL-2
path = /home/me/someImage2.png
}
input-field {
monitor =
size = 200, 50
outline_thickness = 3
outer_color = rgb(151515)
inner_color = rgb(200, 200, 200)
}
```
## Docs
soon:tm:
## Building
### Deps
- wayland-client
- wayland-protocols
- cairo
- gles3.2
- pango
- hyprlang>=0.4.0
- xkbcommon
- pam
### Building
Building:
```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build
cmake --build ./build --config Release --target hyprlock -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
```
Installation:
```sh
sudo cmake --install build
```

View file

@ -0,0 +1,84 @@
#include "ConfigManager.hpp"
#include <filesystem>
static std::string getConfigDir() {
static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
if (xdgConfigHome && std::filesystem::path(xdgConfigHome).is_absolute())
return xdgConfigHome;
return getenv("HOME") + std::string("/.config");
}
static std::string getMainConfigPath() {
return getConfigDir() + "/hypr/hyprlock.conf";
}
CConfigManager::CConfigManager() : m_config(getMainConfigPath().c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}) {
;
}
void CConfigManager::init() {
m_config.addConfigValue("general:disable_loading_bar", Hyprlang::INT{0});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("background", "path", Hyprlang::STRING{""});
m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("input-field", "size", Hyprlang::VEC2{400, 90});
m_config.addSpecialConfigValue("input-field", "inner_color", Hyprlang::INT{0xFFDDDDDD});
m_config.addSpecialConfigValue("input-field", "outer_color", Hyprlang::INT{0xFF111111});
m_config.addSpecialConfigValue("input-field", "outline_thickness", Hyprlang::INT{4});
m_config.commence();
auto result = m_config.parse();
if (result.error)
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
}
std::mutex configMtx;
void* const* CConfigManager::getValuePtr(const std::string& name) {
std::lock_guard<std::mutex> lg(configMtx);
return m_config.getConfigValuePtr(name.c_str())->getDataStaticPtr();
}
std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
std::vector<CConfigManager::SWidgetConfig> result;
//
auto keys = m_config.listKeysForSpecialCategory("background");
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
"background",
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("background", "monitor", k.c_str())),
{
{"path", m_config.getSpecialConfigValue("background", "path", k.c_str())},
}
});
// clang-format on
}
keys = m_config.listKeysForSpecialCategory("input-field");
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
"input-field",
std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("input-field", "monitor", k.c_str())),
{
{"size", m_config.getSpecialConfigValue("input-field", "size", k.c_str())},
{"inner_color", m_config.getSpecialConfigValue("input-field", "inner_color", k.c_str())},
{"outer_color", m_config.getSpecialConfigValue("input-field", "outer_color", k.c_str())},
{"outline_thickness", m_config.getSpecialConfigValue("input-field", "outline_thickness", k.c_str())},
}
});
// clang-format on
}
return result;
}

View file

@ -0,0 +1,30 @@
#pragma once
#include "../helpers/Log.hpp"
#include <hyprlang.hpp>
#include <vector>
#include <memory>
#include <unordered_map>
class CConfigManager {
public:
CConfigManager();
void init();
void* const* getValuePtr(const std::string& name);
struct SWidgetConfig {
std::string type;
std::string monitor;
std::unordered_map<std::string, std::any> values;
};
std::vector<SWidgetConfig> getWidgetConfigs();
private:
Hyprlang::CConfig m_config;
};
inline std::unique_ptr<CConfigManager> g_pConfigManager;

16
src/core/CursorShape.cpp Normal file
View file

@ -0,0 +1,16 @@
#include "CursorShape.hpp"
#include "hyprlock.hpp"
CCursorShape::CCursorShape(wp_cursor_shape_manager_v1* mgr) : mgr(mgr) {
if (!g_pHyprlock->m_pPointer)
return;
dev = wp_cursor_shape_manager_v1_get_pointer(mgr, g_pHyprlock->m_pPointer);
}
void CCursorShape::setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape) {
if (!dev)
return;
wp_cursor_shape_device_v1_set_shape(dev, serial, shape);
}

15
src/core/CursorShape.hpp Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include <wayland-client.h>
#include "cursor-shape-v1-protocol.h"
class CCursorShape {
public:
CCursorShape(wp_cursor_shape_manager_v1* mgr);
void setShape(const uint32_t serial, const wp_cursor_shape_device_v1_shape shape);
private:
wp_cursor_shape_manager_v1* mgr = nullptr;
wp_cursor_shape_device_v1* dev = nullptr;
};

80
src/core/Egl.cpp Normal file
View file

@ -0,0 +1,80 @@
#include "Egl.hpp"
#include "../helpers/Log.hpp"
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT;
const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE,
};
const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION,
2,
EGL_NONE,
};
CEGL::CEGL(wl_display* display) {
const char* _EXTS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (!_EXTS) {
if (eglGetError() == EGL_BAD_DISPLAY)
throw std::runtime_error("EGL_EXT_client_extensions not supported");
else
throw std::runtime_error("Failed to query EGL client extensions");
}
std::string EXTS = _EXTS;
if (!EXTS.contains("EGL_EXT_platform_base"))
throw std::runtime_error("EGL_EXT_platform_base not supported");
if (!EXTS.contains("EGL_EXT_platform_wayland"))
throw std::runtime_error("EGL_EXT_platform_wayland not supported");
eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
if (eglGetPlatformDisplayEXT == NULL)
throw std::runtime_error("Failed to get eglGetPlatformDisplayEXT");
eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
if (eglCreatePlatformWindowSurfaceEXT == NULL)
throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT");
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, NULL);
EGLint matched = 0;
if (eglDisplay == EGL_NO_DISPLAY) {
Debug::log(CRIT, "Failed to create EGL display");
goto error;
}
if (eglInitialize(eglDisplay, NULL, NULL) == EGL_FALSE) {
Debug::log(CRIT, "Failed to initialize EGL");
goto error;
}
if (!eglChooseConfig(eglDisplay, config_attribs, &eglConfig, 1, &matched)) {
Debug::log(CRIT, "eglChooseConfig failed");
goto error;
}
if (matched == 0) {
Debug::log(CRIT, "Failed to match an EGL config");
goto error;
}
eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, context_attribs);
if (eglContext == EGL_NO_CONTEXT) {
Debug::log(CRIT, "Failed to create EGL context");
goto error;
}
return;
error:
eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (eglDisplay)
eglTerminate(eglDisplay);
eglReleaseThread();
}
void CEGL::makeCurrent(EGLSurface surf) {
eglMakeCurrent(eglDisplay, surf, surf, eglContext);
}

22
src/core/Egl.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <wayland-client.h>
#include <memory>
#include <EGL/egl.h>
#include <EGL/eglext.h>
class CEGL {
public:
CEGL(wl_display*);
EGLDisplay eglDisplay;
EGLConfig eglConfig;
EGLContext eglContext;
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT;
void makeCurrent(EGLSurface surf);
};
inline std::unique_ptr<CEGL> g_pEGL;

150
src/core/LockSurface.cpp Normal file
View file

@ -0,0 +1,150 @@
#include "LockSurface.hpp"
#include "hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "Egl.hpp"
#include "../renderer/Renderer.hpp"
static void handleConfigure(void* data, ext_session_lock_surface_v1* surf, uint32_t serial, uint32_t width, uint32_t height) {
const auto PSURF = (CSessionLockSurface*)data;
PSURF->configure({width, height}, serial);
}
static const ext_session_lock_surface_v1_listener lockListener = {
.configure = handleConfigure,
};
static void handlePreferredScale(void* data, wp_fractional_scale_v1* wp_fractional_scale_v1, uint32_t scale) {
const auto PSURF = (CSessionLockSurface*)data;
PSURF->fractionalScale = scale / 120.0;
Debug::log(LOG, "got fractional {}", PSURF->fractionalScale);
}
static const wp_fractional_scale_v1_listener fsListener = {
.preferred_scale = handlePreferredScale,
};
CSessionLockSurface::~CSessionLockSurface() {
if (fractional) {
wp_viewport_destroy(viewport);
wp_fractional_scale_v1_destroy(fractional);
}
wl_egl_window_destroy(eglWindow);
ext_session_lock_surface_v1_destroy(lockSurface);
wl_surface_destroy(surface);
}
CSessionLockSurface::CSessionLockSurface(COutput* output) : output(output) {
surface = wl_compositor_create_surface(g_pHyprlock->getCompositor());
if (!surface) {
Debug::log(CRIT, "Couldn't create wl_surface");
exit(1);
}
fractional = wp_fractional_scale_manager_v1_get_fractional_scale(g_pHyprlock->getFractionalMgr(), surface);
if (fractional) {
wp_fractional_scale_v1_add_listener(fractional, &fsListener, this);
viewport = wp_viewporter_get_viewport(g_pHyprlock->getViewporter(), surface);
wl_display_roundtrip(g_pHyprlock->getDisplay());
} else {
Debug::log(LOG, "No fractional-scale support! Oops, won't be able to scale!");
}
configure(output->size, 0);
g_pRenderer->renderLock(*this);
lockSurface = ext_session_lock_v1_get_lock_surface(g_pHyprlock->getSessionLock(), surface, output->output);
if (!surface) {
Debug::log(CRIT, "Couldn't create ext_session_lock_surface_v1");
exit(1);
}
ext_session_lock_surface_v1_add_listener(lockSurface, &lockListener, this);
wl_display_roundtrip(g_pHyprlock->getDisplay());
wl_display_flush(g_pHyprlock->getDisplay());
}
void CSessionLockSurface::configure(const Vector2D& size_, uint32_t serial_) {
Debug::log(LOG, "configure with serial {}", serial);
serial = serial_;
size = (size_ * fractionalScale).floor();
logicalSize = size_;
if (serial != 0)
ext_session_lock_surface_v1_ack_configure(lockSurface, serial);
if (fractional)
wp_viewport_set_destination(viewport, logicalSize.x, logicalSize.y);
wl_surface_set_buffer_scale(surface, 1);
wl_surface_damage_buffer(surface, 0, 0, 0xFFFF, 0xFFFF);
if (!eglWindow)
eglWindow = wl_egl_window_create(surface, size.x, size.y);
else
wl_egl_window_resize(eglWindow, size.x, size.y, 0, 0);
if (!eglWindow) {
Debug::log(CRIT, "Couldn't create eglWindow");
exit(1);
}
if (serial == 0)
eglSurface = g_pEGL->eglCreatePlatformWindowSurfaceEXT(g_pEGL->eglDisplay, g_pEGL->eglConfig, eglWindow, nullptr);
if (!eglSurface) {
Debug::log(CRIT, "Couldn't create eglSurface");
exit(1);
}
readyForFrame = true;
if (serial != 0)
render();
if (fractional)
wp_viewport_set_destination(viewport, logicalSize.x, logicalSize.y);
wl_surface_set_buffer_scale(surface, 1);
wl_surface_damage_buffer(surface, 0, 0, 0xFFFF, 0xFFFF);
wl_surface_commit(surface);
wl_display_roundtrip(g_pHyprlock->getDisplay());
wl_display_flush(g_pHyprlock->getDisplay());
}
static void handleDone(void* data, wl_callback* wl_callback, uint32_t callback_data) {
const auto PSURF = (CSessionLockSurface*)data;
PSURF->onCallback();
}
static const wl_callback_listener callbackListener = {
.done = handleDone,
};
void CSessionLockSurface::render() {
Debug::log(LOG, "render lock");
const auto FEEDBACK = g_pRenderer->renderLock(*this);
frameCallback = wl_surface_frame(surface);
wl_callback_add_listener(frameCallback, &callbackListener, this);
eglSwapBuffers(g_pEGL->eglDisplay, eglSurface);
if (fractional)
wp_viewport_set_destination(viewport, logicalSize.x, logicalSize.y);
wl_surface_damage_buffer(surface, 0, 0, 0xFFFF, 0xFFFF);
wl_surface_set_buffer_scale(surface, 1);
wl_surface_commit(surface);
needsFrame = FEEDBACK.needsFrame;
}
void CSessionLockSurface::onCallback() {
readyForFrame = true;
frameCallback = nullptr;
if (needsFrame && !g_pHyprlock->m_bTerminate && g_pEGL)
render();
}

46
src/core/LockSurface.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include <wayland-client.h>
#include "ext-session-lock-v1-protocol.h"
#include "viewporter-protocol.h"
#include "fractional-scale-v1-protocol.h"
#include <wayland-egl.h>
#include "../helpers/Vector2D.hpp"
#include <EGL/egl.h>
class COutput;
class CRenderer;
class CSessionLockSurface {
public:
CSessionLockSurface(COutput* output);
~CSessionLockSurface();
void configure(const Vector2D& size, uint32_t serial);
bool readyForFrame = false;
float fractionalScale = 1.0;
void render();
void onCallback();
private:
COutput* output = nullptr;
wl_surface* surface = nullptr;
ext_session_lock_surface_v1* lockSurface = nullptr;
uint32_t serial = 0;
wl_egl_window* eglWindow = nullptr;
Vector2D size;
Vector2D logicalSize;
EGLSurface eglSurface = nullptr;
wp_fractional_scale_v1* fractional = nullptr;
wp_viewport* viewport = nullptr;
bool needsFrame = false;
// wayland callbacks
wl_callback* frameCallback = nullptr;
friend class CRenderer;
};

49
src/core/Output.cpp Normal file
View file

@ -0,0 +1,49 @@
#include "Output.hpp"
#include "../helpers/Log.hpp"
static void handleGeometry(void* data, wl_output* output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make,
const char* model, int32_t transform) {
const auto POUTPUT = (COutput*)data;
POUTPUT->transform = (wl_output_transform)transform;
Debug::log(LOG, "output {} make {} model {}", POUTPUT->name, make ? make : "", model ? model : "");
}
static void handleMode(void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
const auto POUTPUT = (COutput*)data;
POUTPUT->size = {width, height};
}
static void handleDone(void* data, wl_output* output) {
;
}
static void handleScale(void* data, wl_output* output, int32_t factor) {
const auto POUTPUT = (COutput*)data;
POUTPUT->scale = factor;
}
static void handleName(void* data, wl_output* output, const char* name) {
const auto POUTPUT = (COutput*)data;
POUTPUT->stringName = std::string{name} + POUTPUT->stringName;
POUTPUT->stringPort = std::string{name};
Debug::log(LOG, "output {} name {}", POUTPUT->name, name);
}
static void handleDescription(void* data, wl_output* output, const char* description) {
const auto POUTPUT = (COutput*)data;
Debug::log(LOG, "output {} description {}", POUTPUT->name, description ? description : "");
}
static const wl_output_listener outputListener = {
.geometry = handleGeometry,
.mode = handleMode,
.done = handleDone,
.scale = handleScale,
.name = handleName,
.description = handleDescription,
};
COutput::COutput(wl_output* output, uint32_t name) : name(name), output(output) {
wl_output_add_listener(output, &outputListener, this);
}

25
src/core/Output.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <wayland-client.h>
#include "../helpers/Vector2D.hpp"
#include "LockSurface.hpp"
#include <memory>
class COutput {
public:
COutput(wl_output* output, uint32_t name);
uint32_t name = 0;
bool focused = false;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
Vector2D size;
int scale = 1;
std::string stringName = "";
std::string stringPort = "";
std::unique_ptr<CSessionLockSurface> sessionLockSurface;
wl_output* output = nullptr;
private:
};

36
src/core/Password.cpp Normal file
View file

@ -0,0 +1,36 @@
#include "Password.hpp"
#include <unistd.h>
#include <security/pam_appl.h>
#include <security/pam_misc.h>
struct pam_response* reply;
//
int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) {
*resp = reply;
return PAM_SUCCESS;
}
CPassword::SVerificationResult CPassword::verify(const std::string& pass) {
const pam_conv localConv = {conv, NULL};
pam_handle_t* handle = NULL;
int ret = pam_start("su", getlogin(), &localConv, &handle);
if (ret != PAM_SUCCESS)
return {false, "pam_start failed"};
reply = (struct pam_response*)malloc(sizeof(struct pam_response));
reply->resp = strdup(pass.c_str());
reply->resp_retcode = 0;
ret = pam_authenticate(handle, 0);
if (ret != PAM_SUCCESS)
return {false, ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"};
ret = pam_end(handle, ret);
return {true, "Successfully authenticated"};
}

16
src/core/Password.hpp Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <memory>
#include <string>
class CPassword {
public:
struct SVerificationResult {
bool success = false;
std::string failReason = "";
};
SVerificationResult verify(const std::string& pass);
};
inline std::unique_ptr<CPassword> g_pPassword = std::make_unique<CPassword>();

375
src/core/hyprlock.cpp Normal file
View file

@ -0,0 +1,375 @@
#include "hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include "../renderer/Renderer.hpp"
#include "Password.hpp"
#include "Egl.hpp"
#include <sys/mman.h>
#include <cuchar>
CHyprlock::CHyprlock() {
m_sWaylandState.display = wl_display_connect("wayland-2");
if (!m_sWaylandState.display) {
Debug::log(CRIT, "Couldn't connect to a wayland compositor");
exit(1);
}
g_pEGL = std::make_unique<CEGL>(m_sWaylandState.display);
m_pXKBContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!m_pXKBContext)
Debug::log(ERR, "Failed to create xkb context");
g_pRenderer = std::make_unique<CRenderer>();
}
// wl_seat
static void handleCapabilities(void* data, wl_seat* wl_seat, uint32_t capabilities);
static void handleName(void* data, struct wl_seat* wl_seat, const char* name);
inline const wl_seat_listener seatListener = {
.capabilities = handleCapabilities,
.name = handleName,
};
// end wl_seat
// wl_registry
static void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
g_pHyprlock->onGlobal(data, registry, name, interface, version);
}
static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) {
g_pHyprlock->onGlobalRemoved(data, registry, name);
}
inline const wl_registry_listener registryListener = {
.global = handleGlobal,
.global_remove = handleGlobalRemove,
};
void CHyprlock::onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
const std::string IFACE = interface;
Debug::log(LOG, " | got iface: {} v{}", IFACE, version);
if (IFACE == ext_session_lock_manager_v1_interface.name) {
m_sWaylandState.sessionLock = (ext_session_lock_manager_v1*)wl_registry_bind(registry, name, &ext_session_lock_manager_v1_interface, version);
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
} else if (IFACE == wl_seat_interface.name) {
if (m_sWaylandState.seat) {
Debug::log(WARN, "Hyprlock does not support multi-seat configurations. Only binding to the first seat.");
return;
}
m_sWaylandState.seat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, version);
wl_seat_add_listener(m_sWaylandState.seat, &seatListener, nullptr);
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
} else if (IFACE == wl_output_interface.name) {
m_vOutputs.emplace_back(std::make_unique<COutput>((wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version), name));
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
} else if (IFACE == wp_cursor_shape_manager_v1_interface.name) {
m_pCursorShape = std::make_unique<CCursorShape>((wp_cursor_shape_manager_v1*)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, version));
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
} else if (IFACE == wl_compositor_interface.name) {
m_sWaylandState.compositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, version);
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
} else if (IFACE == wp_fractional_scale_manager_v1_interface.name) {
m_sWaylandState.fractional = (wp_fractional_scale_manager_v1*)wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, version);
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
} else if (IFACE == wp_viewporter_interface.name) {
m_sWaylandState.viewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, version);
Debug::log(LOG, " > Bound to {} v{}", IFACE, version);
}
}
void CHyprlock::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) {
Debug::log(LOG, " | removed iface {}", name);
std::erase_if(m_vOutputs, [name](const auto& other) { return other->name == name; });
}
// end wl_registry
void CHyprlock::run() {
m_sWaylandState.registry = wl_display_get_registry(m_sWaylandState.display);
wl_registry_add_listener(m_sWaylandState.registry, &registryListener, nullptr);
wl_display_roundtrip(m_sWaylandState.display);
if (!m_sWaylandState.sessionLock) {
Debug::log(CRIT, "Couldn't bind to ext-session-lock-v1, does your compositor support it?");
exit(1);
}
// gather info about monitors
wl_display_roundtrip(m_sWaylandState.display);
lockSession();
while (wl_display_dispatch(m_sWaylandState.display) != -1) {
if (m_bTerminate)
break;
}
Debug::log(LOG, "Reached the end, exiting");
}
// wl_seat
static void handlePointerEnter(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
if (!g_pHyprlock->m_pCursorShape)
return;
g_pHyprlock->m_pCursorShape->setShape(serial, wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
}
static void handlePointerLeave(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface) {
;
}
static void handlePointerAxis(void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
// ignored
}
static void handlePointerMotion(void* data, struct wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
;
}
static void handlePointerButton(void* data, struct wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) {
;
// TODO: remove xD
g_pHyprlock->unlockSession();
}
static void handleFrame(void* data, struct wl_pointer* wl_pointer) {
;
}
static void handleAxisSource(void* data, struct wl_pointer* wl_pointer, uint32_t axis_source) {
;
}
static void handleAxisStop(void* data, struct wl_pointer* wl_pointer, uint32_t time, uint32_t axis) {
;
}
static void handleAxisDiscrete(void* data, struct wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) {
;
}
static void handleAxisValue120(void* data, struct wl_pointer* wl_pointer, uint32_t axis, int32_t value120) {
;
}
static void handleAxisRelativeDirection(void* data, struct wl_pointer* wl_pointer, uint32_t axis, uint32_t direction) {
;
}
inline const wl_pointer_listener pointerListener = {
.enter = handlePointerEnter,
.leave = handlePointerLeave,
.motion = handlePointerMotion,
.button = handlePointerButton,
.axis = handlePointerAxis,
.frame = handleFrame,
.axis_source = handleAxisSource,
.axis_stop = handleAxisStop,
.axis_discrete = handleAxisDiscrete,
.axis_value120 = handleAxisValue120,
.axis_relative_direction = handleAxisRelativeDirection,
};
static void handleKeyboardKeymap(void* data, wl_keyboard* wl_keyboard, uint format, int fd, uint size) {
if (!g_pHyprlock->m_pXKBContext)
return;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
Debug::log(ERR, "Could not recognise keymap format");
return;
}
const char* buf = (const char*)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED) {
Debug::log(ERR, "Failed to mmap xkb keymap: %d", errno);
return;
}
g_pHyprlock->m_pXKBKeymap = xkb_keymap_new_from_buffer(g_pHyprlock->m_pXKBContext, buf, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap((void*)buf, size);
close(fd);
if (!g_pHyprlock->m_pXKBKeymap) {
Debug::log(ERR, "Failed to compile xkb keymap");
return;
}
g_pHyprlock->m_pXKBState = xkb_state_new(g_pHyprlock->m_pXKBKeymap);
if (!g_pHyprlock->m_pXKBState) {
Debug::log(ERR, "Failed to create xkb state");
return;
}
}
static void handleKeyboardKey(void* data, struct wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
return;
g_pHyprlock->onKey(key);
}
static void handleKeyboardEnter(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface, wl_array* keys) {
;
}
static void handleKeyboardLeave(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface) {
;
}
static void handleKeyboardModifiers(void* data, wl_keyboard* wl_keyboard, uint serial, uint mods_depressed, uint mods_latched, uint mods_locked, uint group) {
if (!g_pHyprlock->m_pXKBState)
return;
xkb_state_update_mask(g_pHyprlock->m_pXKBState, mods_depressed, mods_latched, mods_locked, 0, 0, group);
}
static void handleRepeatInfo(void* data, struct wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) {
;
}
inline const wl_keyboard_listener keyboardListener = {
.keymap = handleKeyboardKeymap,
.enter = handleKeyboardEnter,
.leave = handleKeyboardLeave,
.key = handleKeyboardKey,
.modifiers = handleKeyboardModifiers,
.repeat_info = handleRepeatInfo,
};
static void handleCapabilities(void* data, wl_seat* wl_seat, uint32_t capabilities) {
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
g_pHyprlock->m_pPointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(g_pHyprlock->m_pPointer, &pointerListener, wl_seat);
}
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
g_pHyprlock->m_pKeeb = wl_seat_get_keyboard(wl_seat);
wl_keyboard_add_listener(g_pHyprlock->m_pKeeb, &keyboardListener, wl_seat);
}
}
static void handleName(void* data, struct wl_seat* wl_seat, const char* name) {
;
}
// end wl_seat
// session_lock
static void handleLocked(void* data, ext_session_lock_v1* ext_session_lock_v1) {
g_pHyprlock->onLockLocked();
}
static void handleFinished(void* data, ext_session_lock_v1* ext_session_lock_v1) {
g_pHyprlock->onLockFinished();
}
static const ext_session_lock_v1_listener sessionLockListener = {
.locked = handleLocked,
.finished = handleFinished,
};
// end session_lock
void CHyprlock::onKey(uint32_t key) {
const auto SYM = xkb_state_key_get_one_sym(m_pXKBState, key + 8);
if (SYM == XKB_KEY_BackSpace) {
if (m_sPasswordState.passBuffer.length() > 0)
m_sPasswordState.passBuffer = m_sPasswordState.passBuffer.substr(0, m_sPasswordState.passBuffer.length() - 1);
} else if (SYM == XKB_KEY_Return) {
Debug::log(LOG, "Authenticating");
const auto RESULT = g_pPassword->verify(m_sPasswordState.passBuffer);
Debug::log(LOG, "Password auth result: {}", RESULT.failReason);
if (RESULT.success)
unlockSession();
} else {
char buf[16] = {0};
int len = xkb_keysym_to_utf8(SYM, buf, 16);
if (len > 1)
m_sPasswordState.passBuffer += std::string{buf, len - 1};
}
for (auto& o : m_vOutputs) {
o->sessionLockSurface->render();
}
}
void CHyprlock::lockSession() {
Debug::log(LOG, "Locking session");
m_sLockState.lock = ext_session_lock_manager_v1_lock(m_sWaylandState.sessionLock);
ext_session_lock_v1_add_listener(m_sLockState.lock, &sessionLockListener, nullptr);
}
void CHyprlock::unlockSession() {
Debug::log(LOG, "Unlocking session");
ext_session_lock_v1_unlock_and_destroy(m_sLockState.lock);
m_sLockState.lock = nullptr;
m_vOutputs.clear();
g_pEGL.reset();
Debug::log(LOG, "Unlocked, exiting!");
m_bTerminate = true;
wl_display_roundtrip(m_sWaylandState.display);
}
void CHyprlock::onLockLocked() {
Debug::log(LOG, "onLockLocked called");
for (auto& o : m_vOutputs) {
o->sessionLockSurface = std::make_unique<CSessionLockSurface>(o.get());
}
}
void CHyprlock::onLockFinished() {
Debug::log(LOG, "onLockFinished called. Seems we got yeeten. Is another lockscreen running?");
ext_session_lock_v1_unlock_and_destroy(m_sLockState.lock);
m_sLockState.lock = nullptr;
}
ext_session_lock_manager_v1* CHyprlock::getSessionLockMgr() {
return m_sWaylandState.sessionLock;
}
ext_session_lock_v1* CHyprlock::getSessionLock() {
return m_sLockState.lock;
}
wl_compositor* CHyprlock::getCompositor() {
return m_sWaylandState.compositor;
}
wl_display* CHyprlock::getDisplay() {
return m_sWaylandState.display;
}
wp_fractional_scale_manager_v1* CHyprlock::getFractionalMgr() {
return m_sWaylandState.fractional;
}
wp_viewporter* CHyprlock::getViewporter() {
return m_sWaylandState.viewporter;
}
size_t CHyprlock::getPasswordBufferLen() {
return m_sPasswordState.passBuffer.length();
}

74
src/core/hyprlock.hpp Normal file
View file

@ -0,0 +1,74 @@
#pragma once
#include <wayland-client.h>
#include "ext-session-lock-v1-protocol.h"
#include "fractional-scale-v1-protocol.h"
#include "viewporter-protocol.h"
#include "Output.hpp"
#include "CursorShape.hpp"
#include <memory>
#include <vector>
#include <xkbcommon/xkbcommon.h>
class CHyprlock {
public:
CHyprlock();
void run();
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);
void onLockLocked();
void onLockFinished();
void lockSession();
void unlockSession();
void onKey(uint32_t key);
size_t getPasswordBufferLen();
ext_session_lock_manager_v1* getSessionLockMgr();
ext_session_lock_v1* getSessionLock();
wl_compositor* getCompositor();
wl_display* getDisplay();
wp_fractional_scale_manager_v1* getFractionalMgr();
wp_viewporter* getViewporter();
wl_pointer* m_pPointer = nullptr;
wl_keyboard* m_pKeeb = nullptr;
std::unique_ptr<CCursorShape> m_pCursorShape;
xkb_context* m_pXKBContext = nullptr;
xkb_keymap* m_pXKBKeymap = nullptr;
xkb_state* m_pXKBState = nullptr;
bool m_bTerminate = false;
private:
struct {
wl_display* display = nullptr;
wl_registry* registry = nullptr;
wl_seat* seat = nullptr;
ext_session_lock_manager_v1* sessionLock = nullptr;
wl_compositor* compositor = nullptr;
wp_fractional_scale_manager_v1* fractional = nullptr;
wp_viewporter* viewporter = nullptr;
} m_sWaylandState;
struct {
ext_session_lock_v1* lock = nullptr;
} m_sLockState;
struct {
std::string passBuffer = "";
} m_sPasswordState;
std::vector<std::unique_ptr<COutput>> m_vOutputs;
};
inline std::unique_ptr<CHyprlock> g_pHyprlock;

101
src/helpers/Box.cpp Normal file
View file

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

69
src/helpers/Box.hpp Normal file
View file

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

26
src/helpers/Color.cpp Normal file
View file

@ -0,0 +1,26 @@
#include "Color.hpp"
#define ALPHA(c) ((double)(((c) >> 24) & 0xff) / 255.0)
#define RED(c) ((double)(((c) >> 16) & 0xff) / 255.0)
#define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0)
#define BLUE(c) ((double)(((c)) & 0xff) / 255.0)
CColor::CColor() {}
CColor::CColor(float r, float g, float b, float a) {
this->r = r;
this->g = g;
this->b = b;
this->a = a;
}
CColor::CColor(uint64_t hex) {
this->r = RED(hex);
this->g = GREEN(hex);
this->b = BLUE(hex);
this->a = ALPHA(hex);
}
uint64_t CColor::getAsHex() {
return ((int)a) * 0x1000000 + ((int)r) * 0x10000 + ((int)g) * 0x100 + ((int)b) * 0x1;
}

34
src/helpers/Color.hpp Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include <cstdint>
class CColor {
public:
CColor();
CColor(float r, float g, float b, float a);
CColor(uint64_t);
float r = 0, g = 0, b = 0, a = 1.f;
uint64_t getAsHex();
CColor operator-(const CColor& c2) const {
return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
}
CColor operator+(const CColor& c2) const {
return CColor(r + c2.r, g + c2.g, b + c2.b, a + c2.a);
}
CColor operator*(const float& v) const {
return CColor(r * v, g * v, b * v, a * v);
}
bool operator==(const CColor& c2) const {
return r == c2.r && g == c2.g && b == c2.b && a == c2.a;
}
CColor stripA() const {
return {r, g, b, 1};
}
};

58
src/helpers/Log.hpp Normal file
View file

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

51
src/helpers/Vector2D.cpp Normal file
View file

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

154
src/helpers/Vector2D.hpp Normal file
View file

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

31
src/main.cpp Normal file
View file

@ -0,0 +1,31 @@
#include "config/ConfigManager.hpp"
#include "core/hyprlock.hpp"
int main(int argc, char **argv, char **envp) {
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--verbose" || arg == "-v")
Debug::verbose = true;
else if (arg == "--quiet" || arg == "-q")
Debug::quiet = true;
}
try {
g_pConfigManager = std::make_unique<CConfigManager>();
g_pConfigManager->init();
} catch (const char *err) {
Debug::log(CRIT, "ConfigManager threw: {}", err);
std::string strerr = err;
if (strerr.contains("File does not exist"))
Debug::log(NONE, " Make sure you have a config.");
return 1;
}
g_pHyprlock = std::make_unique<CHyprlock>();
g_pHyprlock->run();
return 0;
}

View file

@ -0,0 +1,92 @@
#include "AsyncResourceGatherer.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/Egl.hpp"
#include <cairo/cairo.h>
#include <algorithm>
CAsyncResourceGatherer::CAsyncResourceGatherer() {
thread = std::thread([this]() { this->gather(); });
thread.detach();
}
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
for (auto& a : assets) {
if (a.first == id)
return &a.second;
}
return nullptr;
}
void CAsyncResourceGatherer::gather() {
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
g_pEGL->makeCurrent(nullptr);
// gather resources to preload
// clang-format off
int preloads = std::count_if(CWIDGETS.begin(), CWIDGETS.end(), [](const auto& w) {
return w.type == "background";
});
// clang-format on
progress = 0;
for (auto& c : CWIDGETS) {
if (c.type == "background") {
progress += 1.0 / (preloads + 1.0);
std::string path = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
std::string id = std::string{"background:"} + path;
// preload bg img
const auto CAIROISURFACE = cairo_image_surface_create_from_png(path.c_str());
const auto CAIRO = cairo_create(CAIROISURFACE);
cairo_scale(CAIRO, 1, 1);
const auto TARGET = &preloadTargets.emplace_back(CAsyncResourceGatherer::SPreloadTarget{});
TARGET->size = {cairo_image_surface_get_width(CAIROISURFACE), cairo_image_surface_get_height(CAIROISURFACE)};
TARGET->type = TARGET_IMAGE;
TARGET->id = id;
const auto DATA = cairo_image_surface_get_data(CAIROISURFACE);
TARGET->cairo = CAIRO;
TARGET->cairosurface = CAIROISURFACE;
TARGET->data = DATA;
}
}
ready = true;
}
void CAsyncResourceGatherer::apply() {
for (auto& t : preloadTargets) {
if (t.type == TARGET_IMAGE) {
const auto ASSET = &assets[t.id];
const auto CAIROFORMAT = cairo_image_surface_get_format((cairo_surface_t*)t.cairosurface);
const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA;
const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA;
const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE;
ASSET->texture.m_vSize = t.size;
ASSET->texture.allocate();
glBindTexture(GL_TEXTURE_2D, ASSET->texture.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data);
cairo_destroy((cairo_t*)t.cairo);
cairo_surface_destroy((cairo_surface_t*)t.cairosurface);
}
}
applied = true;
}

View file

@ -0,0 +1,50 @@
#pragma once
#include "Shader.hpp"
#include "../helpers/Box.hpp"
#include "../helpers/Color.hpp"
#include "Texture.hpp"
#include <thread>
#include <atomic>
#include <vector>
#include <unordered_map>
struct SPreloadedAsset {
CTexture texture;
};
class CAsyncResourceGatherer {
public:
CAsyncResourceGatherer();
std::atomic<bool> ready = false;
std::atomic<bool> applied = false;
std::atomic<float> progress = 0;
SPreloadedAsset* getAssetByID(const std::string& id);
void apply();
private:
std::thread thread;
enum eTargetType {
TARGET_IMAGE = 0,
};
struct SPreloadTarget {
eTargetType type = TARGET_IMAGE;
std::string id = "";
void* data;
void* cairo;
void* cairosurface;
Vector2D size;
};
std::vector<SPreloadTarget> preloadTargets;
std::unordered_map<std::string, SPreloadedAsset> assets;
void gather();
};

262
src/renderer/Renderer.cpp Normal file
View file

@ -0,0 +1,262 @@
#include "Renderer.hpp"
#include "../core/Egl.hpp"
#include "../config/ConfigManager.hpp"
#include "../helpers/Color.hpp"
#include "../core/Output.hpp"
#include "mtx.hpp"
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <algorithm>
#include "Shaders.hpp"
#include "widgets/PasswordInputField.hpp"
#include "widgets/Background.hpp"
inline const float fullVerts[] = {
1, 0, // top right
0, 0, // top left
1, 1, // bottom right
0, 1, // bottom left
};
GLuint compileShader(const GLuint& type, std::string src) {
auto shader = glCreateShader(type);
auto shaderSource = src.c_str();
glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr);
glCompileShader(shader);
GLint ok;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!");
return shader;
}
GLuint createProgram(const std::string& vert, const std::string& frag) {
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert);
RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert.c_str());
auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag);
RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT NULL! Shader source:\n\n{}", frag.c_str());
auto prog = glCreateProgram();
glAttachShader(prog, vertCompiled);
glAttachShader(prog, fragCompiled);
glLinkProgram(prog);
glDetachShader(prog, vertCompiled);
glDetachShader(prog, fragCompiled);
glDeleteShader(vertCompiled);
glDeleteShader(fragCompiled);
GLint ok;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!");
return prog;
}
static void glMessageCallbackA(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
if (type != GL_DEBUG_TYPE_ERROR)
return;
Debug::log(LOG, "[gl] {}", (const char*)message);
}
CRenderer::CRenderer() {
g_pEGL->makeCurrent(nullptr);
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(glMessageCallbackA, 0);
GLuint prog = createProgram(QUADVERTSRC, QUADFRAGSRC);
rectShader.program = prog;
rectShader.proj = glGetUniformLocation(prog, "proj");
rectShader.color = glGetUniformLocation(prog, "color");
rectShader.posAttrib = glGetAttribLocation(prog, "pos");
rectShader.topLeft = glGetUniformLocation(prog, "topLeft");
rectShader.fullSize = glGetUniformLocation(prog, "fullSize");
rectShader.radius = glGetUniformLocation(prog, "radius");
prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA);
texShader.program = prog;
texShader.proj = glGetUniformLocation(prog, "proj");
texShader.tex = glGetUniformLocation(prog, "tex");
texShader.alphaMatte = glGetUniformLocation(prog, "texMatte");
texShader.alpha = glGetUniformLocation(prog, "alpha");
texShader.texAttrib = glGetAttribLocation(prog, "texcoord");
texShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte");
texShader.posAttrib = glGetAttribLocation(prog, "pos");
texShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
texShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
texShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
texShader.topLeft = glGetUniformLocation(prog, "topLeft");
texShader.fullSize = glGetUniformLocation(prog, "fullSize");
texShader.radius = glGetUniformLocation(prog, "radius");
texShader.applyTint = glGetUniformLocation(prog, "applyTint");
texShader.tint = glGetUniformLocation(prog, "tint");
texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte");
wlr_matrix_identity(projMatrix.data());
asyncResourceGatherer = std::make_unique<CAsyncResourceGatherer>();
}
static int frames = 0;
//
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
static auto* const PDISABLEBAR = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:disable_loading_bar");
matrixProjection(projection.data(), surf.size.x, surf.size.y, WL_OUTPUT_TRANSFORM_NORMAL);
g_pEGL->makeCurrent(surf.eglSurface);
glViewport(0, 0, surf.size.x, surf.size.y);
glScissor(frames, 0, surf.size.x, surf.size.y);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
float bga = 0;
SRenderFeedback feedback;
if (!asyncResourceGatherer->ready) {
// render status
if (!**PDISABLEBAR) {
CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2};
renderRect(progress, CColor{0.2f, 0.1f, 0.1f, 1.f}, 0);
}
} else {
if (!asyncResourceGatherer->applied) {
asyncResourceGatherer->apply();
gatheredAt = std::chrono::system_clock::now();
}
// render widgets
const auto WIDGETS = getOrCreateWidgetsFor(&surf);
for (auto& w : *WIDGETS) {
feedback.needsFrame = w->draw() || feedback.needsFrame;
}
}
frames++;
Debug::log(LOG, "frame {}", frames);
feedback.needsFrame = feedback.needsFrame || bga < 1.0;
return feedback;
}
void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) {
float matrix[9];
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
projMatrix.data()); // TODO: write own, don't use WLR here
float glMatrix[9];
wlr_matrix_multiply(glMatrix, projection.data(), matrix);
glUseProgram(rectShader.program);
glUniformMatrix3fv(rectShader.proj, 1, GL_TRUE, glMatrix);
// premultiply the color as well as we don't work with straight alpha
glUniform4f(rectShader.color, col.r * col.a, col.g * col.a, col.b * col.a, col.a);
const auto TOPLEFT = Vector2D(box.x, box.y);
const auto FULLSIZE = Vector2D(box.width, box.height);
// Rounded corners
glUniform2f(rectShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(rectShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform1f(rectShader.radius, rounding);
glVertexAttribPointer(rectShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(rectShader.posAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(rectShader.posAttrib);
}
void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding) {
float matrix[9];
wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_FLIPPED_180 /* ugh coordinate spaces */, 0,
projMatrix.data()); // TODO: write own, don't use WLR here
float glMatrix[9];
wlr_matrix_multiply(glMatrix, projection.data(), matrix);
CShader* shader = &texShader;
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex.m_iTarget, tex.m_iTexID);
glUseProgram(shader->program);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix);
glUniform1i(shader->tex, 0);
glUniform1f(shader->alpha, a);
const auto TOPLEFT = Vector2D(box.x, box.y);
const auto FULLSIZE = Vector2D(box.width, box.height);
// Rounded corners
glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y);
glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y);
glUniform1f(shader->radius, rounding);
glUniform1i(shader->discardOpaque, 0);
glUniform1i(shader->discardAlpha, 0);
glUniform1i(shader->applyTint, 0);
glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts);
glEnableVertexAttribArray(shader->posAttrib);
glEnableVertexAttribArray(shader->texAttrib);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(shader->posAttrib);
glDisableVertexAttribArray(shader->texAttrib);
glBindTexture(tex.m_iTarget, 0);
}
std::vector<std::unique_ptr<IWidget>>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) {
if (!widgets.contains(surf)) {
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
for (auto& c : CWIDGETS) {
if (!c.monitor.empty() && c.monitor != surf->output->stringPort)
continue;
// by type
if (c.type == "background")
widgets[surf].emplace_back(std::make_unique<CBackground>(surf->size, std::string{"background:"} + std::any_cast<Hyprlang::STRING>(c.values.at("path"))));
if (c.type == "input-field") {
const auto SIZE = std::any_cast<Hyprlang::VEC2>(c.values.at("size"));
widgets[surf].emplace_back(std::make_unique<CPasswordInputField>(surf->size, Vector2D{SIZE.x, SIZE.y}, std::any_cast<Hyprlang::INT>(c.values.at("outer_color")),
std::any_cast<Hyprlang::INT>(c.values.at("inner_color")),
std::any_cast<Hyprlang::INT>(c.values.at("outline_thickness"))));
}
}
}
return &widgets[surf];
}

43
src/renderer/Renderer.hpp Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include <memory>
#include <chrono>
#include "../core/LockSurface.hpp"
#include "Shader.hpp"
#include "../helpers/Box.hpp"
#include "../helpers/Color.hpp"
#include "AsyncResourceGatherer.hpp"
#include "widgets/IWidget.hpp"
typedef std::unordered_map<const CSessionLockSurface*, std::vector<std::unique_ptr<IWidget>>> widgetMap_t;
class CRenderer {
public:
CRenderer();
struct SRenderFeedback {
bool needsFrame = false;
};
SRenderFeedback renderLock(const CSessionLockSurface& surface);
void renderRect(const CBox& box, const CColor& col, int rounding = 0);
void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0);
std::unique_ptr<CAsyncResourceGatherer> asyncResourceGatherer;
std::chrono::system_clock::time_point gatheredAt;
private:
widgetMap_t widgets;
std::vector<std::unique_ptr<IWidget>>* getOrCreateWidgetsFor(const CSessionLockSurface* surf);
CShader rectShader;
CShader texShader;
std::array<float, 9> projMatrix;
std::array<float, 9> projection;
};
inline std::unique_ptr<CRenderer> g_pRenderer;

23
src/renderer/Shader.cpp Normal file
View file

@ -0,0 +1,23 @@
#include "Shader.hpp"
GLint CShader::getUniformLocation(const std::string& unif) {
const auto itpos = m_muUniforms.find(unif);
if (itpos == m_muUniforms.end()) {
const auto unifLoc = glGetUniformLocation(program, unif.c_str());
m_muUniforms[unif] = unifLoc;
return unifLoc;
}
return itpos->second;
}
CShader::~CShader() {
destroy();
}
void CShader::destroy() {
glDeleteProgram(program);
program = 0;
}

68
src/renderer/Shader.hpp Normal file
View file

@ -0,0 +1,68 @@
#pragma once
#include <unordered_map>
#include <GLES3/gl32.h>
#include <string>
class CShader {
public:
~CShader();
GLuint program = 0;
GLint proj = -1;
GLint color = -1;
GLint alphaMatte = -1;
GLint tex = -1;
GLint alpha = -1;
GLint posAttrib = -1;
GLint texAttrib = -1;
GLint matteTexAttrib = -1;
GLint discardOpaque = -1;
GLint discardAlpha = -1;
GLfloat discardAlphaValue = -1;
GLint topLeft = -1;
GLint bottomRight = -1;
GLint fullSize = -1;
GLint fullSizeUntransformed = -1;
GLint radius = -1;
GLint radiusOuter = -1;
GLint thick = -1;
GLint halfpixel = -1;
GLint range = -1;
GLint shadowPower = -1;
GLint useAlphaMatte = -1; // always inverted
GLint applyTint = -1;
GLint tint = -1;
GLint gradient = -1;
GLint gradientLength = -1;
GLint angle = -1;
GLint time = -1;
GLint distort = -1;
GLint wl_output = -1;
// Blur prepare
GLint contrast = -1;
// Blur
GLint passes = -1; // Used by `vibrancy`
GLint vibrancy = -1;
GLint vibrancy_darkness = -1;
// Blur finish
GLint brightness = -1;
GLint noise = -1;
GLint getUniformLocation(const std::string&);
void destroy();
private:
std::unordered_map<std::string, GLint> m_muUniforms;
};

122
src/renderer/Shaders.hpp Normal file
View file

@ -0,0 +1,122 @@
#pragma once
#include <string>
inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVarName) -> std::string {
return R"#(
// branchless baby!
highp vec2 pixCoord = vec2(gl_FragCoord);
pixCoord -= topLeft + fullSize * 0.5;
pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0;
pixCoord -= fullSize * 0.5 - radius;
pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left
if (pixCoord.x + pixCoord.y > radius) {
float dist = length(pixCoord);
if (dist > radius + 1.0)
discard;
if (dist > radius - 1.0) {
float dist = length(pixCoord);
float normalized = 1.0 - smoothstep(0.0, 1.0, dist - radius + 0.5);
)#" +
colorVarName + R"#( = )#" + colorVarName + R"#( * normalized;
}
}
)#";
};
inline const std::string QUADVERTSRC = R"#(
uniform mat3 proj;
uniform vec4 color;
attribute vec2 pos;
attribute vec2 texcoord;
attribute vec2 texcoordMatte;
varying vec4 v_color;
varying vec2 v_texcoord;
varying vec2 v_texcoordMatte;
void main() {
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
v_color = color;
v_texcoord = texcoord;
v_texcoordMatte = texcoordMatte;
})#";
inline const std::string QUADFRAGSRC = R"#(
precision highp float;
varying vec4 v_color;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform float radius;
void main() {
vec4 pixColor = v_color;
if (radius > 0.0) {
)#" +
ROUNDED_SHADER_FUNC("pixColor") + R"#(
}
gl_FragColor = pixColor;
})#";
inline const std::string TEXVERTSRC = R"#(
uniform mat3 proj;
attribute vec2 pos;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
v_texcoord = texcoord;
})#";
inline const std::string TEXFRAGSRCRGBA = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
uniform float alpha;
uniform vec2 topLeft;
uniform vec2 fullSize;
uniform float radius;
uniform int discardOpaque;
uniform int discardAlpha;
uniform float discardAlphaValue;
uniform int applyTint;
uniform vec3 tint;
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
if (discardOpaque == 1 && pixColor[3] * alpha == 1.0)
discard;
if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue)
discard;
if (applyTint == 1) {
pixColor[0] = pixColor[0] * tint[0];
pixColor[1] = pixColor[1] * tint[1];
pixColor[2] = pixColor[2] * tint[2];
}
if (radius > 0.0) {
)#" +
ROUNDED_SHADER_FUNC("pixColor") + R"#(
}
gl_FragColor = pixColor * alpha;
})#";

19
src/renderer/Texture.cpp Normal file
View file

@ -0,0 +1,19 @@
#include "Texture.hpp"
CTexture::CTexture() {
// naffin'
}
void CTexture::destroyTexture() {
if (m_bAllocated) {
glDeleteTextures(1, &m_iTexID);
m_iTexID = 0;
}
m_bAllocated = false;
}
void CTexture::allocate() {
if (!m_bAllocated)
glGenTextures(1, &m_iTexID);
m_bAllocated = true;
}

25
src/renderer/Texture.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <GLES3/gl32.h>
#include "../helpers/Vector2D.hpp"
enum TEXTURETYPE {
TEXTURE_INVALID, // Invalid
TEXTURE_RGBA, // 4 channels
TEXTURE_RGBX, // discard A
TEXTURE_EXTERNAL, // EGLImage
};
class CTexture {
public:
CTexture();
void destroyTexture();
void allocate();
TEXTURETYPE m_iType = TEXTURE_RGBA;
GLenum m_iTarget = GL_TEXTURE_2D;
bool m_bAllocated = false;
GLuint m_iTexID = 0;
Vector2D m_vSize;
};

236
src/renderer/mtx.hpp Normal file
View file

@ -0,0 +1,236 @@
#pragma once
#include <cstring>
#include <wayland-client.h>
#include "../helpers/Box.hpp"
enum wl_output_transform wlr_output_transform_invert(enum wl_output_transform tr) {
// if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) {
// tr ^= WL_OUTPUT_TRANSFORM_180;
// }
return tr;
}
void wlr_matrix_identity(float mat[9]) {
const float identity[9] = {
1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
};
memcpy(mat, identity, sizeof(identity));
}
void wlr_matrix_multiply(float mat[9], const float a[9], const float b[9]) {
float product[9];
product[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
product[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
product[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
product[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
product[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
product[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
product[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
product[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
product[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
memcpy(mat, product, sizeof(product));
}
void wlr_matrix_transpose(float mat[9], const float a[9]) {
float transposition[9] = {
a[0], a[3], a[6], a[1], a[4], a[7], a[2], a[5], a[8],
};
memcpy(mat, transposition, sizeof(transposition));
}
void wlr_matrix_translate(float mat[9], float x, float y) {
float translate[9] = {
1.0f, 0.0f, x, 0.0f, 1.0f, y, 0.0f, 0.0f, 1.0f,
};
wlr_matrix_multiply(mat, mat, translate);
}
void wlr_matrix_scale(float mat[9], float x, float y) {
float scale[9] = {
x, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 1.0f,
};
wlr_matrix_multiply(mat, mat, scale);
}
void wlr_matrix_rotate(float mat[9], float rad) {
float rotate[9] = {
cos(rad), -sin(rad), 0.0f, sin(rad), cos(rad), 0.0f, 0.0f, 0.0f, 1.0f,
};
wlr_matrix_multiply(mat, mat, rotate);
}
const float transforms[][9] = {
[WL_OUTPUT_TRANSFORM_NORMAL] =
{
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
[WL_OUTPUT_TRANSFORM_90] =
{
0.0f,
1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
[WL_OUTPUT_TRANSFORM_180] =
{
-1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
[WL_OUTPUT_TRANSFORM_270] =
{
0.0f,
-1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED] =
{
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_90] =
{
0.0f,
1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_180] =
{
1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_270] =
{
0.0f,
-1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
};
void wlr_matrix_transform(float mat[9], enum wl_output_transform transform) {
wlr_matrix_multiply(mat, mat, transforms[transform]);
}
void matrix_projection(float mat[9], int width, int height, enum wl_output_transform transform) {
std::memset(mat, 0, sizeof(*mat) * 9);
const float* t = transforms[transform];
float x = 2.0f / width;
float y = 2.0f / height;
// Rotation + reflection
mat[0] = x * t[0];
mat[1] = x * t[1];
mat[3] = y * -t[3];
mat[4] = y * -t[4];
// Translation
mat[2] = -copysign(1.0f, mat[0] + mat[1]);
mat[5] = -copysign(1.0f, mat[3] + mat[4]);
// Identity
mat[8] = 1.0f;
}
void wlr_matrix_project_box(float mat[9], const CBox* box, enum wl_output_transform transform, float rotation, const float projection[9]) {
int x = box->x;
int y = box->y;
int width = box->width;
int height = box->height;
wlr_matrix_identity(mat);
wlr_matrix_translate(mat, x, y);
if (rotation != 0) {
wlr_matrix_translate(mat, width / 2, height / 2);
wlr_matrix_rotate(mat, rotation);
wlr_matrix_translate(mat, -width / 2, -height / 2);
}
wlr_matrix_scale(mat, width, height);
if (transform != WL_OUTPUT_TRANSFORM_NORMAL) {
wlr_matrix_translate(mat, 0.5, 0.5);
wlr_matrix_transform(mat, transform);
wlr_matrix_translate(mat, -0.5, -0.5);
}
wlr_matrix_multiply(mat, projection, mat);
}
void matrixProjection(float mat[9], int w, int h, wl_output_transform tr) {
memset(mat, 0, sizeof(*mat) * 9);
const float* t = transforms[tr];
float x = 2.0f / w;
float y = 2.0f / h;
// Rotation + reflection
mat[0] = x * t[0];
mat[1] = x * t[1];
mat[3] = y * t[3];
mat[4] = y * t[4];
// Translation
mat[2] = -copysign(1.0f, mat[0] + mat[1]);
mat[5] = -copysign(1.0f, mat[3] + mat[4]);
// Identity
mat[8] = 1.0f;
}

View file

@ -0,0 +1,21 @@
#include "Background.hpp"
#include "../Renderer.hpp"
CBackground::CBackground(const Vector2D& viewport_, const std::string& resourceID_) : viewport(viewport_), resourceID(resourceID_) {
;
}
bool CBackground::draw() {
if (!asset)
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset)
return false;
float bga = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - g_pRenderer->gatheredAt).count() / 500000.0, 0.0, 1.0);
CBox monbox = {0, 0, viewport.x, viewport.y};
g_pRenderer->renderTexture(monbox, asset->texture, bga);
return bga < 1.0;
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "IWidget.hpp"
#include "../../helpers/Vector2D.hpp"
#include <string>
struct SPreloadedAsset;
class CBackground : public IWidget {
public:
CBackground(const Vector2D& viewport, const std::string& resourceID);
virtual bool draw();
private:
Vector2D viewport;
std::string resourceID;
SPreloadedAsset* asset = nullptr;
};

View file

@ -0,0 +1,6 @@
#pragma once
class IWidget {
public:
virtual bool draw() = 0;
};

View file

@ -0,0 +1,62 @@
#include "PasswordInputField.hpp"
#include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include <algorithm>
CPasswordInputField::CPasswordInputField(const Vector2D& viewport, const Vector2D& size_, const CColor& outer_, const CColor& inner_, int out_thick_) {
size = size_;
pos = viewport / 2.f - size_ / 2.f;
inner = inner_;
outer = outer_;
out_thick = out_thick_;
}
void CPasswordInputField::updateDots() {
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
size_t dotsAppearingOrPresent = std::count_if(dots.begin(), dots.end(), [](const auto& dot) { return dot.appearing || !dot.animated; });
if (dotsAppearingOrPresent < PASSLEN) {
dots.push_back(dot{.idx = dotsAppearingOrPresent + 1, .appearing = true, .animated = true, .a = 0, .start = std::chrono::system_clock::now()});
} else if (dotsAppearingOrPresent > PASSLEN) {
dots[dots.size() - 1].animated = true;
dots[dots.size() - 1].appearing = false;
dots[dots.size() - 1].start = std::chrono::system_clock::now();
}
for (auto& dot : dots) {
if (dot.appearing) {
if (dot.a < 1.0)
dot.a = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dot.start).count() / 100000.0, 0.0, 1.0);
} else {
if (dot.a > 0.0)
dot.a = std::clamp(1.0 - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dot.start).count() / 100000.0, 0.0, 1.0);
}
if (dot.appearing && dot.a == 1.0)
dot.animated = false;
}
std::erase_if(dots, [](const auto& dot) { return !dot.appearing && dot.a == 0.0; });
}
bool CPasswordInputField::draw() {
CBox inputFieldBox = {pos, size};
CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}};
updateDots();
g_pRenderer->renderRect(outerBox, outer, outerBox.h / 2.0);
g_pRenderer->renderRect(inputFieldBox, inner, inputFieldBox.h / 2.0);
constexpr int PASS_SPACING = 3;
constexpr int PASS_SIZE = 8;
for (size_t i = 0; i < dots.size(); ++i) {
Vector2D currentPos = inputFieldBox.pos() + Vector2D{PASS_SPACING, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * dots[i].idx, 0};
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
g_pRenderer->renderRect(box, CColor{0, 0, 0, dots[i].a}, PASS_SIZE / 2.0);
}
return std::ranges::any_of(dots.begin(), dots.end(), [](const auto& dot) { return dot.animated; });
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "IWidget.hpp"
#include "../../helpers/Vector2D.hpp"
#include "../../helpers/Color.hpp"
#include <chrono>
#include <vector>
class CPasswordInputField : public IWidget {
public:
CPasswordInputField(const Vector2D& viewport, const Vector2D& size, const CColor& outer, const CColor& inner, int out_thick);
virtual bool draw();
private:
void updateDots();
Vector2D size;
Vector2D pos;
int out_thick;
CColor inner, outer;
struct dot {
size_t idx = 0;
bool appearing = false;
bool animated = false;
float a = 0;
std::chrono::system_clock::time_point start;
};
std::vector<dot> dots;
};