diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..90314ef
--- /dev/null
+++ b/.clang-format
@@ -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
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..33419bc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.vscode/
+build/
+protocols/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..4b29316
--- /dev/null
+++ b/CMakeLists.txt
@@ -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)
diff --git a/README.md b/README.md
index cc04a83..f4b72e0 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,63 @@
 # 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
+```
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
new file mode 100644
index 0000000..7a309a8
--- /dev/null
+++ b/src/config/ConfigManager.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp
new file mode 100644
index 0000000..c0ab72b
--- /dev/null
+++ b/src/config/ConfigManager.hpp
@@ -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;
diff --git a/src/core/CursorShape.cpp b/src/core/CursorShape.cpp
new file mode 100644
index 0000000..365348d
--- /dev/null
+++ b/src/core/CursorShape.cpp
@@ -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);
+}
\ No newline at end of file
diff --git a/src/core/CursorShape.hpp b/src/core/CursorShape.hpp
new file mode 100644
index 0000000..65b0f39
--- /dev/null
+++ b/src/core/CursorShape.hpp
@@ -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;
+};
\ No newline at end of file
diff --git a/src/core/Egl.cpp b/src/core/Egl.cpp
new file mode 100644
index 0000000..71956a6
--- /dev/null
+++ b/src/core/Egl.cpp
@@ -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);
+}
\ No newline at end of file
diff --git a/src/core/Egl.hpp b/src/core/Egl.hpp
new file mode 100644
index 0000000..55f1db0
--- /dev/null
+++ b/src/core/Egl.hpp
@@ -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;
\ No newline at end of file
diff --git a/src/core/LockSurface.cpp b/src/core/LockSurface.cpp
new file mode 100644
index 0000000..089ad63
--- /dev/null
+++ b/src/core/LockSurface.cpp
@@ -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();
+}
\ No newline at end of file
diff --git a/src/core/LockSurface.hpp b/src/core/LockSurface.hpp
new file mode 100644
index 0000000..2f93521
--- /dev/null
+++ b/src/core/LockSurface.hpp
@@ -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;
+};
\ No newline at end of file
diff --git a/src/core/Output.cpp b/src/core/Output.cpp
new file mode 100644
index 0000000..cebdab3
--- /dev/null
+++ b/src/core/Output.cpp
@@ -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);
+}
\ No newline at end of file
diff --git a/src/core/Output.hpp b/src/core/Output.hpp
new file mode 100644
index 0000000..5525874
--- /dev/null
+++ b/src/core/Output.hpp
@@ -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:
+};
\ No newline at end of file
diff --git a/src/core/Password.cpp b/src/core/Password.cpp
new file mode 100644
index 0000000..ee749be
--- /dev/null
+++ b/src/core/Password.cpp
@@ -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"};
+}
\ No newline at end of file
diff --git a/src/core/Password.hpp b/src/core/Password.hpp
new file mode 100644
index 0000000..951c180
--- /dev/null
+++ b/src/core/Password.hpp
@@ -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>();
\ No newline at end of file
diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp
new file mode 100644
index 0000000..8770518
--- /dev/null
+++ b/src/core/hyprlock.cpp
@@ -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();
+}
diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp
new file mode 100644
index 0000000..7e82e18
--- /dev/null
+++ b/src/core/hyprlock.hpp
@@ -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;
\ No newline at end of file
diff --git a/src/helpers/Box.cpp b/src/helpers/Box.cpp
new file mode 100644
index 0000000..9b88218
--- /dev/null
+++ b/src/helpers/Box.cpp
@@ -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};
+}
\ No newline at end of file
diff --git a/src/helpers/Box.hpp b/src/helpers/Box.hpp
new file mode 100644
index 0000000..c5f30d3
--- /dev/null
+++ b/src/helpers/Box.hpp
@@ -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();
+};
diff --git a/src/helpers/Color.cpp b/src/helpers/Color.cpp
new file mode 100644
index 0000000..77e0d53
--- /dev/null
+++ b/src/helpers/Color.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/helpers/Color.hpp b/src/helpers/Color.hpp
new file mode 100644
index 0000000..98ffa47
--- /dev/null
+++ b/src/helpers/Color.hpp
@@ -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};
+    }
+};
diff --git a/src/helpers/Log.hpp b/src/helpers/Log.hpp
new file mode 100644
index 0000000..e11da90
--- /dev/null
+++ b/src/helpers/Log.hpp
@@ -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";
+    }
+};
\ No newline at end of file
diff --git a/src/helpers/Vector2D.cpp b/src/helpers/Vector2D.cpp
new file mode 100644
index 0000000..4cd3476
--- /dev/null
+++ b/src/helpers/Vector2D.cpp
@@ -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));
+}
diff --git a/src/helpers/Vector2D.hpp b/src/helpers/Vector2D.hpp
new file mode 100644
index 0000000..0c2b9ce
--- /dev/null
+++ b/src/helpers/Vector2D.hpp
@@ -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); }
+    }
+};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..def4a43
--- /dev/null
+++ b/src/main.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp
new file mode 100644
index 0000000..8a33cde
--- /dev/null
+++ b/src/renderer/AsyncResourceGatherer.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/renderer/AsyncResourceGatherer.hpp b/src/renderer/AsyncResourceGatherer.hpp
new file mode 100644
index 0000000..6c0f8a0
--- /dev/null
+++ b/src/renderer/AsyncResourceGatherer.hpp
@@ -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();
+};
\ No newline at end of file
diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp
new file mode 100644
index 0000000..c6a0cf2
--- /dev/null
+++ b/src/renderer/Renderer.cpp
@@ -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];
+}
diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp
new file mode 100644
index 0000000..e07536b
--- /dev/null
+++ b/src/renderer/Renderer.hpp
@@ -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;
\ No newline at end of file
diff --git a/src/renderer/Shader.cpp b/src/renderer/Shader.cpp
new file mode 100644
index 0000000..b494d79
--- /dev/null
+++ b/src/renderer/Shader.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/renderer/Shader.hpp b/src/renderer/Shader.hpp
new file mode 100644
index 0000000..e123632
--- /dev/null
+++ b/src/renderer/Shader.hpp
@@ -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;
+};
\ No newline at end of file
diff --git a/src/renderer/Shaders.hpp b/src/renderer/Shaders.hpp
new file mode 100644
index 0000000..0eca6a9
--- /dev/null
+++ b/src/renderer/Shaders.hpp
@@ -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;
+})#";
\ No newline at end of file
diff --git a/src/renderer/Texture.cpp b/src/renderer/Texture.cpp
new file mode 100644
index 0000000..96b4987
--- /dev/null
+++ b/src/renderer/Texture.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/renderer/Texture.hpp b/src/renderer/Texture.hpp
new file mode 100644
index 0000000..56f0e67
--- /dev/null
+++ b/src/renderer/Texture.hpp
@@ -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;
+};
\ No newline at end of file
diff --git a/src/renderer/mtx.hpp b/src/renderer/mtx.hpp
new file mode 100644
index 0000000..372d6bc
--- /dev/null
+++ b/src/renderer/mtx.hpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp
new file mode 100644
index 0000000..413e728
--- /dev/null
+++ b/src/renderer/widgets/Background.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp
new file mode 100644
index 0000000..a99aa1d
--- /dev/null
+++ b/src/renderer/widgets/Background.hpp
@@ -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;
+};
\ No newline at end of file
diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp
new file mode 100644
index 0000000..1f667dd
--- /dev/null
+++ b/src/renderer/widgets/IWidget.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+class IWidget {
+  public:
+    virtual bool draw() = 0;
+};
\ No newline at end of file
diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp
new file mode 100644
index 0000000..1b7dbbc
--- /dev/null
+++ b/src/renderer/widgets/PasswordInputField.cpp
@@ -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; });
+}
\ No newline at end of file
diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp
new file mode 100644
index 0000000..eb98968
--- /dev/null
+++ b/src/renderer/widgets/PasswordInputField.hpp
@@ -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;
+};
\ No newline at end of file