diff --git a/CMakeLists.txt b/CMakeLists.txt index 92fafef..10093fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,7 @@ protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false) protocolnew("staging/fractional-scale" "fractional-scale-v1" false) protocolnew("stable/viewporter" "viewporter" false) protocolnew("staging/cursor-shape" "cursor-shape-v1" false) +protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false) protocolnew("stable/tablet" "tablet-v2" false) protocolnew("unstable/text-input" "text-input-unstable-v3" false) protocolnew("staging/linux-drm-syncobj" "linux-drm-syncobj-v1" false) @@ -143,6 +144,11 @@ add_executable(controls "tests/Controls.cpp") target_link_libraries(controls PRIVATE PkgConfig::deps hyprtoolkit) add_dependencies(tests controls) +add_executable(simpleSessionLock "tests/SimpleSessionLock.cpp") +target_link_libraries(simpleSessionLock PRIVATE PkgConfig::deps hyprtoolkit) +add_dependencies(tests simpleSessionLock) + + # Installation install(TARGETS hyprtoolkit) install(DIRECTORY "include/hyprtoolkit" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/flake.lock b/flake.lock index 2259b61..29aa36b 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ ] }, "locked": { - "lastModified": 1759499898, - "narHash": "sha256-UNzYHLWfkSzLHDep5Ckb5tXc0fdxwPIrT+MY4kpQttM=", + "lastModified": 1761420899, + "narHash": "sha256-kxGCip6GNbcbNWKu4J2iKbNYfFTS8Zbjg9CWp0zmFoM=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "655e067f96fd44b3f5685e17f566b0e4d535d798", + "rev": "62479232aae42c1ef09c2c027c8cfd91df060897", "type": "github" }, "original": { @@ -42,11 +42,11 @@ ] }, "locked": { - "lastModified": 1759490292, - "narHash": "sha256-T6iWzDOXp8Wv0KQOCTHpBcmAOdHJ6zc/l9xaztW6Ivc=", + "lastModified": 1760445448, + "narHash": "sha256-fXGjL6dw31FPFRrmIemzGiNSlfvEJTJNsmadZi+qNhI=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "9431db625cd9bb66ac55525479dce694101d6d7a", + "rev": "50fb9f069219f338a11cf0bcccb9e58357d67757", "type": "github" }, "original": { @@ -91,11 +91,11 @@ ] }, "locked": { - "lastModified": 1759609323, - "narHash": "sha256-5c9sJ4CFdmYJ/EcIUSyzo3CZu3fR+k5r7lnOrtZ8sWA=", + "lastModified": 1759619523, + "narHash": "sha256-r1ed7AR2ZEb2U8gy321/Xcp1ho2tzn+gG1te/Wxsj1A=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "9ab64319e95374934aac5406df4e69fee77345ff", + "rev": "3df7bde01efb3a3e8e678d1155f2aa3f19e177ef", "type": "github" }, "original": { @@ -129,11 +129,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1759381078, - "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", + "lastModified": 1761373498, + "narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 41b1355..5c83fa1 100644 --- a/flake.nix +++ b/flake.nix @@ -73,6 +73,18 @@ inherit (pkgsFor.${system}) hyprtoolkit; }); + devShells = eachSystem (system: { + default = + pkgsFor.${system}.mkShell.override { + inherit (self.packages.${system}.default) stdenv; + } { + name = "hyprtoolkit-shell"; + hardeningDisable = ["fortify"]; + inputsFrom = [pkgsFor.${system}.hyprtoolkit]; + packages = [pkgsFor.${system}.clang-tools]; + }; + }); + checks = eachSystem (system: self.packages.${system}); formatter = eachSystem (system: pkgsFor.${system}.alejandra); diff --git a/include/hyprtoolkit/core/Backend.hpp b/include/hyprtoolkit/core/Backend.hpp index 47d4862..c2d2892 100644 --- a/include/hyprtoolkit/core/Backend.hpp +++ b/include/hyprtoolkit/core/Backend.hpp @@ -5,16 +5,19 @@ #include #include #include +#include #include #include #include "LogTypes.hpp" +#include "SessionLock.hpp" #include "../palette/Palette.hpp" #include "CoreMacros.hpp" namespace Hyprtoolkit { class IWindow; + class IOutput; class CTimer; class ISystemIconFactory; struct SWindowCreationData; @@ -73,6 +76,25 @@ namespace Hyprtoolkit { virtual Hyprutils::Memory::CSharedPointer getPalette() = 0; + /* + Get currently registered outputs. + Make sure you register the `removed` event to get rid of your reference once the output is removed. + */ + virtual std::vector> getOutputs() = 0; + + /* + Create and lock the graphical session. + It is required to call this before HT_WINDOW_LOCK_SURFACE can be used. + */ + virtual std::expected, eSessionLockError> aquireSessionLock() = 0; + + struct { + /* + Get notified when a new output was added. + */ + Hyprutils::Signal::CSignalT> outputAdded; + } m_events; + protected: IBackend(); }; diff --git a/include/hyprtoolkit/core/Output.hpp b/include/hyprtoolkit/core/Output.hpp new file mode 100644 index 0000000..1b55865 --- /dev/null +++ b/include/hyprtoolkit/core/Output.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace Hyprtoolkit { + class IOutput { + public: + virtual ~IOutput() = default; + virtual uint32_t handle() = 0; + virtual std::string port() = 0; + virtual std::string desc() = 0; + virtual uint32_t fps() = 0; + + struct { + /* output removed */ + Hyprutils::Signal::CSignalT<> removed; + } m_events; + }; +} diff --git a/include/hyprtoolkit/core/SessionLock.hpp b/include/hyprtoolkit/core/SessionLock.hpp new file mode 100644 index 0000000..2bc553b --- /dev/null +++ b/include/hyprtoolkit/core/SessionLock.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +namespace Hyprtoolkit { + enum eSessionLockError : uint8_t { + PLATFORM_UNINTIALIZED, + DENIED, + }; + + class ISessionLockState { + public: + virtual ~ISessionLockState() = default; + virtual void unlock() = 0; + + struct { + /* signals that we don't need to unlock anymore. It makes sense to exit upon recieving this */ + Hyprutils::Signal::CSignalT<> finished; + } m_events; + }; +} diff --git a/include/hyprtoolkit/window/Window.hpp b/include/hyprtoolkit/window/Window.hpp index 4cc3e83..51b09fa 100644 --- a/include/hyprtoolkit/window/Window.hpp +++ b/include/hyprtoolkit/window/Window.hpp @@ -9,12 +9,14 @@ namespace Hyprtoolkit { class IElement; class IWindow; + class IOutput; struct SWindowCreationData; enum eWindowType : uint8_t { - HT_WINDOW_TOPLEVEL = 0, - HT_WINDOW_POPUP = 1, - HT_WINDOW_LAYER = 2, + HT_WINDOW_TOPLEVEL = 0, + HT_WINDOW_POPUP = 1, + HT_WINDOW_LAYER = 2, + HT_WINDOW_LOCK_SURFACE = 3, }; class CWindowBuilder { @@ -28,6 +30,8 @@ namespace Hyprtoolkit { Hyprutils::Memory::CSharedPointer preferredSize(const Hyprutils::Math::Vector2D&); Hyprutils::Memory::CSharedPointer minSize(const Hyprutils::Math::Vector2D&); Hyprutils::Memory::CSharedPointer maxSize(const Hyprutils::Math::Vector2D&); + // TODO: implement for window types other than HT_WINDOW_LOCK_SURFACE + Hyprutils::Memory::CSharedPointer prefferedOutput(const Hyprutils::Memory::CSharedPointer& output); // only for HT_WINDOW_LAYER Hyprutils::Memory::CSharedPointer marginTopLeft(const Hyprutils::Math::Vector2D&); diff --git a/src/core/Backend.cpp b/src/core/Backend.cpp index 93e7652..b09f5f8 100644 --- a/src/core/Backend.cpp +++ b/src/core/Backend.cpp @@ -8,12 +8,15 @@ #include "./platforms/WaylandPlatform.hpp" #include "../renderer/gl/OpenGL.hpp" -#include "../window/WaylandWindow.hpp" +#include "../output/WaylandOutput.hpp" #include "../window/WaylandLayer.hpp" +#include "../window/WaylandLockSurface.hpp" +#include "../window/WaylandWindow.hpp" #include "../Macros.hpp" #include "../element/Element.hpp" #include "../palette/ConfigManager.hpp" #include "../system/Icons.hpp" +#include "../sessionLock/WaylandSessionLock.hpp" #include #include @@ -68,6 +71,13 @@ SP IBackend::create() { g_logger->log(HT_LOG_ERROR, "couldn't start aq backend"); return nullptr; } + g_waylandPlatform = makeUnique(); + if (!g_waylandPlatform->attempt()) { + g_waylandPlatform = nullptr; + return nullptr; + } + g_openGL = makeShared(g_waylandPlatform->m_drmState.fd); + g_renderer = g_openGL; return g_backend; }; @@ -84,14 +94,27 @@ SP CBackend::getPalette() { return g_palette; } +std::vector> CBackend::getOutputs() { + if (!g_waylandPlatform) + return {}; + + return std::vector>(g_waylandPlatform->m_outputs.begin(), g_waylandPlatform->m_outputs.end()); +} + +std::expected, eSessionLockError> CBackend::aquireSessionLock() { + if (!g_waylandPlatform) + return std::unexpected(PLATFORM_UNINTIALIZED); + + auto lockState = g_waylandPlatform->aquireSessionLock(); + if (!lockState || lockState->m_denied) + return std::unexpected(DENIED); + + return lockState; +} + SP CBackend::openWindow(const SWindowCreationData& data) { - if (!g_waylandPlatform) { - g_waylandPlatform = makeUnique(); - if (!g_waylandPlatform->attempt()) - return nullptr; - g_openGL = makeShared(g_waylandPlatform->m_drmState.fd); - g_renderer = g_openGL; - } + if (!g_waylandPlatform) + return nullptr; if (data.type == HT_WINDOW_LAYER) { if (!g_waylandPlatform->m_waylandState.layerShell) @@ -102,6 +125,20 @@ SP CBackend::openWindow(const SWindowCreationData& data) { w->m_rootElement->impl->window = w; g_waylandPlatform->m_layers.emplace_back(w); return w; + } else if (data.type == HT_WINDOW_LOCK_SURFACE) { + if (!g_waylandPlatform->m_waylandState.sessionLock) { + g_logger->log(HT_LOG_ERROR, "No session lock manager. Does your compositor support it?"); + return nullptr; + } + + if (!g_waylandPlatform->m_sessionLockState || g_waylandPlatform->m_sessionLockState->m_denied || g_waylandPlatform->m_sessionLockState->m_sessionUnlocked) + return nullptr; + + auto w = makeShared(data); + w->m_self = w; + w->m_rootElement->impl->window = w; + g_waylandPlatform->m_sessionLockState->m_lockSurfaces.emplace_back(w); + return w; } auto w = makeShared(data); diff --git a/src/core/Backend.hpp b/src/core/Backend.hpp index bb7674e..48f41ef 100644 --- a/src/core/Backend.hpp +++ b/src/core/Backend.hpp @@ -23,10 +23,12 @@ namespace Hyprtoolkit { virtual void addFd(int fd, std::function&& callback); virtual void removeFd(int fd); virtual SP systemIcons(); - virtual ASP addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force = false); - virtual void addIdle(const std::function& fn); - virtual void enterLoop(); - virtual SP getPalette(); + virtual ASP addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force = false); + virtual void addIdle(const std::function& fn); + virtual void enterLoop(); + virtual std::vector> getOutputs(); + virtual SP getPalette(); + virtual std::expected, eSessionLockError> aquireSessionLock(); // ======================= Internal fns ======================= // diff --git a/src/core/platforms/WaylandPlatform.cpp b/src/core/platforms/WaylandPlatform.cpp index 23b4918..210819a 100644 --- a/src/core/platforms/WaylandPlatform.cpp +++ b/src/core/platforms/WaylandPlatform.cpp @@ -1,7 +1,9 @@ #include "WaylandPlatform.hpp" #include +#include #include +#include #include #include "../InternalBackend.hpp" @@ -9,6 +11,9 @@ #include "../../element/Element.hpp" #include "../../window/WaylandWindow.hpp" #include "../../window/WaylandLayer.hpp" +#include "../../window/WaylandLockSurface.hpp" +#include "../../output/WaylandOutput.hpp" +#include "../../sessionLock/WaylandSessionLock.hpp" #include "../../Macros.hpp" #include @@ -95,9 +100,30 @@ bool CWaylandPlatform::attempt() { g_logger->log(HT_LOG_ERROR, "Wayland platform cannot start: zwp_linux_dmabuf_v1 init failed"); m_waylandState.dmabufFailed = true; } + } else if (NAME == wl_output_interface.name) { + TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 4, id)); + auto newOutput = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &wl_output_interface, 4), id); + m_outputs.emplace_back(newOutput); + if (m_waylandState.initialized) + g_backend->m_events.outputAdded.emit(newOutput); + } else if (NAME == ext_session_lock_manager_v1_interface.name) { + TRACE(g_logger->log(HT_LOG_TRACE, " > binding to global: {} (version {}) with id {}", name, 1, id)); + m_waylandState.sessionLock = makeShared( + (wl_proxy*)wl_registry_bind((wl_registry*)m_waylandState.registry->resource(), id, &ext_session_lock_manager_v1_interface, 1)); + } + }); + m_waylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t id) { + TRACE(g_logger->log(HT_LOG_TRACE, "Global {} removed", id)); + + if (m_sessionLockState) + m_sessionLockState->onOutputRemoved(id); + + auto outputIt = std::ranges::find_if(m_outputs, [id](const auto& other) { return other->m_id == id; }); + if (outputIt != m_outputs.end()) { + (*outputIt)->m_events.removed.emit(); + m_outputs.erase(outputIt); } }); - m_waylandState.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { g_logger->log(HT_LOG_DEBUG, "Global {} removed", id); }); wl_display_roundtrip(m_waylandState.display); @@ -111,13 +137,16 @@ bool CWaylandPlatform::attempt() { dispatchEvents(); + m_waylandState.initialized = true; + for (const auto& o : m_outputs) { + g_backend->m_events.outputAdded.emit(o); + } + return true; } CWaylandPlatform::~CWaylandPlatform() { - const auto DPY = m_waylandState.display; - m_waylandState = {}; - wl_display_disconnect(DPY); + m_outputs.clear(); if (m_drmState.fd >= 0) close(m_drmState.fd); @@ -128,6 +157,10 @@ CWaylandPlatform::~CWaylandPlatform() { xkb_keymap_unref(m_waylandState.seatState.xkbKeymap); if (m_waylandState.seatState.xkbContext) xkb_context_unref(m_waylandState.seatState.xkbContext); + + const auto DPY = m_waylandState.display; + m_waylandState = {}; + wl_display_disconnect(DPY); } bool CWaylandPlatform::dispatchEvents() { @@ -145,12 +178,6 @@ bool CWaylandPlatform::dispatchEvents() { wl_display_flush(m_waylandState.display); } while (ret > 0); - // dispatch frames - for (auto const& f : m_idleCallbacks) { - f(); - } - m_idleCallbacks.clear(); - return true; } @@ -179,9 +206,24 @@ SP CWaylandPlatform::windowForSurf(wl_proxy* proxy) { return pp; } } + + if (m_sessionLockState) { + for (const auto& w : m_sessionLockState->m_lockSurfaces) { + if (w->m_waylandState.surface && w->m_waylandState.surface->resource() == proxy) + return w.lock(); + } + } return nullptr; } +WP CWaylandPlatform::outputForHandle(uint32_t handle) { + for (const auto& o : m_outputs) { + if (o->m_id == handle) + return o; + } + return SP{}; +} + void CWaylandPlatform::initIM() { m_waylandState.textInput = makeShared(m_waylandState.textInputManager->sendGetTextInput(m_waylandState.seat->resource())); @@ -600,3 +642,17 @@ void CWaylandPlatform::stopRepeatTimer() { m_waylandState.seatState.repeatTimer->cancel(); m_waylandState.seatState.repeatTimer.reset(); } + +SP CWaylandPlatform::aquireSessionLock() { + if (m_sessionLockState) + return m_sessionLockState.lock(); + + auto sessionLock = makeShared(makeShared(m_waylandState.sessionLock->sendLock())); + + // roundtrip in case the compositor sends `finished` right away + wl_display_roundtrip(m_waylandState.display); + + m_sessionLockState = sessionLock; + + return sessionLock; +} diff --git a/src/core/platforms/WaylandPlatform.hpp b/src/core/platforms/WaylandPlatform.hpp index ed881a2..ba2179f 100644 --- a/src/core/platforms/WaylandPlatform.hpp +++ b/src/core/platforms/WaylandPlatform.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../../helpers/Memory.hpp" @@ -23,6 +24,7 @@ #include #include #include +#include #include #include @@ -33,32 +35,34 @@ namespace Hyprtoolkit { class CWaylandWindow; class IWaylandWindow; class CWaylandLayer; + class CWaylandOutput; + class CWaylandSessionLockState; class CWaylandPlatform { public: CWaylandPlatform() = default; ~CWaylandPlatform(); - bool attempt(); + bool attempt(); - void initSeat(); - void initShell(); - bool initDmabuf(); - void initIM(); - void setCursor(ePointerShape shape); + void initSeat(); + void initShell(); + bool initDmabuf(); + void initIM(); + void setCursor(ePointerShape shape); - bool dispatchEvents(); + bool dispatchEvents(); - SP windowForSurf(wl_proxy* proxy); + SP windowForSurf(wl_proxy* proxy); + WP outputForHandle(uint32_t handle); - void onKey(uint32_t keycode, bool state); - void startRepeatTimer(); - void stopRepeatTimer(); + void onKey(uint32_t keycode, bool state); + void startRepeatTimer(); + void stopRepeatTimer(); - void onRepeatTimerFire(); + void onRepeatTimerFire(); - // - std::vector m_idleCallbacks; + SP aquireSessionLock(); // dmabuf formats std::vector m_dmabufFormats; @@ -86,8 +90,10 @@ namespace Hyprtoolkit { Hyprutils::Memory::CSharedPointer textInput; Hyprutils::Memory::CSharedPointer layerShell; Hyprutils::Memory::CSharedPointer syncobj; + Hyprutils::Memory::CSharedPointer sessionLock; // control + bool initialized = false; bool dmabufFailed = false; struct { @@ -118,11 +124,15 @@ namespace Hyprtoolkit { std::string nodeName = ""; } m_drmState; + std::vector> m_outputs; + std::vector> m_windows; std::vector> m_layers; WP m_currentWindow; uint32_t m_currentMods = 0; // HT modifiers, not xkb uint32_t m_lastEnterSerial = 0; + + WP m_sessionLockState; }; inline UP g_waylandPlatform; diff --git a/src/output/WaylandOutput.cpp b/src/output/WaylandOutput.cpp new file mode 100644 index 0000000..bebb27a --- /dev/null +++ b/src/output/WaylandOutput.cpp @@ -0,0 +1,73 @@ +#include "WaylandOutput.hpp" +#include "../core/InternalBackend.hpp" +#include "../helpers/Memory.hpp" + +using namespace Hyprtoolkit; + +static Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) { + switch (t) { + case WL_OUTPUT_TRANSFORM_NORMAL: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; + case WL_OUTPUT_TRANSFORM_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_180; + case WL_OUTPUT_TRANSFORM_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_90; + case WL_OUTPUT_TRANSFORM_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_270; + case WL_OUTPUT_TRANSFORM_FLIPPED: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90; + default: break; + } + return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; +} + +CWaylandOutput::CWaylandOutput(wl_proxy* wlResource, uint32_t id) : m_id(id), m_wlOutput(makeShared(wlResource)) { + m_wlOutput->setDescription([this](CCWlOutput* r, const char* description) { + m_configuration.desc = description ? std::string{description} : ""; + g_logger->log(HT_LOG_DEBUG, "wayland output {}: description {}", m_id, m_configuration.desc); + }); + + m_wlOutput->setName([this](CCWlOutput* r, const char* name) { + m_configuration.name = std::string{name} + m_configuration.name; + m_configuration.port = std::string{name}; + g_logger->log(HT_LOG_DEBUG, "wayland output {}: name {}", m_id, name); + }); + + m_wlOutput->setScale([this](CCWlOutput* r, int32_t sc) { m_configuration.scale = sc; }); + + m_wlOutput->setDone([this](CCWlOutput* r) { + m_configuration.done = true; + g_logger->log(HT_LOG_DEBUG, "wayland output {}: done", m_id); + }); + + m_wlOutput->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + // handle portrait mode and flipped cases + if (m_configuration.transform % 2 == 1) + m_configuration.size = {height, width}; + else + m_configuration.size = {width, height}; + + g_logger->log(HT_LOG_DEBUG, "wayland output {}: dimensions {}", m_id, m_configuration.size); + }); + + m_wlOutput->setGeometry( + [this](CCWlOutput* r, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform_) { + m_configuration.transform = wlTransformToHyprutils((wl_output_transform)transform_); + + g_logger->log(HT_LOG_DEBUG, "wayland output {}: make {} model {}", m_id, make ? make : "", model ? model : ""); + }); +} + +uint32_t CWaylandOutput::handle() { + return m_id; +} + +std::string CWaylandOutput::port() { + return m_configuration.port; +} + +std::string CWaylandOutput::desc() { + return m_configuration.desc; +} + +uint32_t CWaylandOutput::fps() { + return m_configuration.fps; +} diff --git a/src/output/WaylandOutput.hpp b/src/output/WaylandOutput.hpp new file mode 100644 index 0000000..d14d9ef --- /dev/null +++ b/src/output/WaylandOutput.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "wayland.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace Hyprtoolkit { + class CWaylandOutput : public IOutput { + public: + CWaylandOutput(wl_proxy* wlResource, uint32_t id); + ~CWaylandOutput() = default; + + virtual uint32_t handle(); + virtual std::string port(); + virtual std::string desc(); + virtual uint32_t fps(); + + uint32_t m_id = 0; + bool m_focused = false; + Hyprutils::Memory::CSharedPointer m_wlOutput = nullptr; + + struct { + bool done = false; + Hyprutils::Math::eTransform transform = Hyprutils::Math::HYPRUTILS_TRANSFORM_NORMAL; + Hyprutils::Math::Vector2D size; + uint32_t fps = 60; + uint32_t scale = 1; + std::string name = ""; + std::string port = ""; + std::string desc = ""; + } m_configuration; + }; +} diff --git a/src/renderer/gl/OpenGL.cpp b/src/renderer/gl/OpenGL.cpp index 9d41565..fa1c00d 100644 --- a/src/renderer/gl/OpenGL.cpp +++ b/src/renderer/gl/OpenGL.cpp @@ -522,6 +522,8 @@ COpenGLRenderer::COpenGLRenderer(int drmFD) : m_drmFD(drmFD) { } COpenGLRenderer::~COpenGLRenderer() { + m_glTextures.clear(); + if (m_eglDisplay && m_eglContext != EGL_NO_CONTEXT) eglDestroyContext(m_eglDisplay, m_eglContext); diff --git a/src/sessionLock/WaylandSessionLock.cpp b/src/sessionLock/WaylandSessionLock.cpp new file mode 100644 index 0000000..7ceeb98 --- /dev/null +++ b/src/sessionLock/WaylandSessionLock.cpp @@ -0,0 +1,50 @@ +#include "WaylandSessionLock.hpp" +#include "../core/InternalBackend.hpp" +#include "../core/platforms/WaylandPlatform.hpp" +#include "../window/WaylandLockSurface.hpp" + +using namespace Hyprtoolkit; + +CWaylandSessionLockState::CWaylandSessionLockState(SP lock) : m_lock(lock) { + m_lock->setLocked([this](CCExtSessionLockV1* r) { m_sessionLocked = true; }); + m_lock->setFinished([this](CCExtSessionLockV1* r) { + g_logger->log(HT_LOG_ERROR, "We got denied by the compositor to be the exclusive lock screen client. Is there another lock screen active?"); + m_denied = true; + m_events.finished.emit(); + }); +} + +void CWaylandSessionLockState::unlock() { + if (m_sessionUnlocked) { + g_logger->log(HT_LOG_WARNING, "Double unlock in WaylandSessionLockState!"); + return; + } + + if (!m_lock) { + g_logger->log(HT_LOG_WARNING, "Unlock without an active sessionLock object in WaylandSessionLockState!"); + return; + } + + m_lock->sendUnlockAndDestroy(); + m_lock.reset(); + m_sessionUnlocked = true; + + // roundtrip in order to make sure we have unlocked before sending closeRequest + wl_display_roundtrip(g_waylandPlatform->m_waylandState.display); + + for (const auto& sls : m_lockSurfaces) { + if (sls.expired()) + continue; + sls->m_events.closeRequest.emit(); + } + + m_lockSurfaces.clear(); +} + +void CWaylandSessionLockState::onOutputRemoved(uint32_t outputHandle) { + auto lockSurfaceIt = std::ranges::find_if(m_lockSurfaces, [outputHandle](const auto& other) { return other->m_outputHandle == outputHandle; }); + if (lockSurfaceIt != m_lockSurfaces.end()) { + (*lockSurfaceIt)->m_events.closeRequest.emit(); + m_lockSurfaces.erase(lockSurfaceIt); + } +} diff --git a/src/sessionLock/WaylandSessionLock.hpp b/src/sessionLock/WaylandSessionLock.hpp new file mode 100644 index 0000000..e589cbe --- /dev/null +++ b/src/sessionLock/WaylandSessionLock.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "wayland.hpp" + +#include "../helpers/Memory.hpp" +#include +#include + +namespace Hyprtoolkit { + class CWaylandLockSurface; + + class CWaylandSessionLockState : public ISessionLockState { + public: + CWaylandSessionLockState(SP lock); + ~CWaylandSessionLockState() = default; + + virtual void unlock(); + + void onOutputRemoved(uint32_t outputHandle); + + std::vector> m_lockSurfaces; + SP m_lock = nullptr; + bool m_sessionLocked = false; + bool m_sessionUnlocked = false; + bool m_denied = false; // set on the finished event + }; +} diff --git a/src/window/Builder.cpp b/src/window/Builder.cpp index ac4d596..2b0a08d 100644 --- a/src/window/Builder.cpp +++ b/src/window/Builder.cpp @@ -1,5 +1,6 @@ #include "Window.hpp" #include "../core/InternalBackend.hpp" +#include #include "ToolkitWindow.hpp" using namespace Hyprtoolkit; @@ -41,6 +42,11 @@ SP CWindowBuilder::maxSize(const Hyprutils::Math::Vector2D& x) { return m_self.lock(); } +SP CWindowBuilder::prefferedOutput(const SP& x) { + m_data->prefferedOutputId = x->handle(); + return m_self.lock(); +} + SP CWindowBuilder::parent(const SP& x) { m_data->parent = x; return m_self.lock(); @@ -94,7 +100,8 @@ SP CWindowBuilder::commence() { return reinterpretPointerCast(m_data->parent)->openPopup(*m_data); case HT_WINDOW_TOPLEVEL: - case HT_WINDOW_LAYER: return g_backend->openWindow(*m_data); + case HT_WINDOW_LAYER: + case HT_WINDOW_LOCK_SURFACE: return g_backend->openWindow(*m_data); } return nullptr; -} \ No newline at end of file +} diff --git a/src/window/WaylandLockSurface.cpp b/src/window/WaylandLockSurface.cpp new file mode 100644 index 0000000..9180576 --- /dev/null +++ b/src/window/WaylandLockSurface.cpp @@ -0,0 +1,127 @@ +#include "WaylandLockSurface.hpp" + +#include +#include +#include + +#include "../core/AnimationManager.hpp" +#include "../core/InternalBackend.hpp" +#include "../core/platforms/WaylandPlatform.hpp" +#include "../element/Element.hpp" +#include "../output/WaylandOutput.hpp" +#include "../renderer/Renderer.hpp" +#include "../sessionLock/WaylandSessionLock.hpp" + +#include "../Macros.hpp" + +using namespace Hyprtoolkit; +using namespace Hyprutils::Math; + +CWaylandLockSurface::CWaylandLockSurface(const SWindowCreationData& data) : m_outputHandle(data.prefferedOutputId) { + m_rootElement = CNullBuilder::begin()->commence(); +} + +CWaylandLockSurface::~CWaylandLockSurface() { + close(); +} + +void CWaylandLockSurface::open() { + if (m_open) + return; + + if (m_outputHandle == 0) { + g_logger->log(HT_LOG_ERROR, "session lock missing prefferedOutputId"); + return; + } + + if (!g_waylandPlatform || !g_waylandPlatform->m_sessionLockState) { + g_logger->log(HT_LOG_ERROR, "wayland platform not initialized"); + return; + } + + auto lockObject = g_waylandPlatform->m_sessionLockState->m_lock; + if (!lockObject) + return; + + auto wlOutput = g_waylandPlatform->outputForHandle(m_outputHandle); + if (!wlOutput) + return; + + m_open = true; + + m_rootElement->impl->window = m_self; + m_rootElement->impl->breadthfirst([this](SP e) { e->impl->window = m_self; }); + + if (!m_waylandState.surface) { + m_waylandState.surface = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateSurface()); + if (!m_waylandState.surface->resource()) { + g_logger->log(HT_LOG_ERROR, "lock surface opening failed: no surface given. Errno: {}", errno); + return; + } + + auto inputRegion = makeShared(g_waylandPlatform->m_waylandState.compositor->sendCreateRegion()); + inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); + + m_waylandState.surface->sendSetInputRegion(inputRegion.get()); + + m_waylandState.fractional = makeShared(g_waylandPlatform->m_waylandState.fractional->sendGetFractionalScale(m_waylandState.surface->resource())); + + m_waylandState.fractional->setPreferredScale([this](CCWpFractionalScaleV1*, uint32_t scale) { + const bool SAMESCALE = m_fractionalScale == scale / 120.0; + m_fractionalScale = scale / 120.0; + + g_logger->log(HT_LOG_DEBUG, "lock surface: got fractional scale: {:.1f}%", m_fractionalScale * 100.F); + + if (!SAMESCALE && m_lockSurfaceState.configured) + onScaleUpdate(); + }); + + m_waylandState.viewport = makeShared(g_waylandPlatform->m_waylandState.viewporter->sendGetViewport(m_waylandState.surface->resource())); + } else + m_waylandState.surface->sendAttach(nullptr, 0, 0); + + m_lockSurfaceState.lockSurface = makeShared(lockObject->sendGetLockSurface(m_waylandState.surface->resource(), wlOutput->m_wlOutput->resource())); + if (!m_lockSurfaceState.lockSurface->resource()) { + g_logger->log(HT_LOG_ERROR, "lock surface opening failed: no lock surface. Errno: {}", errno); + return; + } + + m_lockSurfaceState.lockSurface->setConfigure([this](CCExtSessionLockSurfaceV1* r, uint32_t serial, uint32_t w, uint32_t h) { + g_logger->log(HT_LOG_DEBUG, "wayland: configure layer with {}x{}", w, h); + m_lockSurfaceState.configured = true; + if (w == 0 || h == 0) { + g_logger->log(HT_LOG_ERROR, "configure: w/h is 0, for a lock surface is bogus. Still, trying with 1920x1080..."); + w = 1920; + h = 1080; + } + + if (m_waylandState.logicalSize == Vector2D{sc(w), sc(h)}) + return; + + m_lockSurfaceState.lockSurface->sendAckConfigure(serial); + + configure(Vector2D{sc(w), sc(h)}, m_waylandState.serial); + + m_events.resized.emit(m_waylandState.logicalSize); + }); +} + +void CWaylandLockSurface::close() { + if (!m_open) + return; + + m_open = false; + + m_waylandState.frameCallback.reset(); + + m_lockSurfaceState.lockSurface.reset(); + m_waylandState.logicalSize = {}; + m_lockSurfaceState.configured = false; +} + +void CWaylandLockSurface::render() { + if (!m_lockSurfaceState.configured) + return; + + IWaylandWindow::render(); +} diff --git a/src/window/WaylandLockSurface.hpp b/src/window/WaylandLockSurface.hpp new file mode 100644 index 0000000..1e957c0 --- /dev/null +++ b/src/window/WaylandLockSurface.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "IWaylandWindow.hpp" +#include + +namespace Hyprtoolkit { + class CWaylandLockSurface : public IWaylandWindow { + public: + CWaylandLockSurface(const SWindowCreationData& data); + virtual ~CWaylandLockSurface(); + + virtual void close(); + virtual void open(); + virtual void render(); + + private: + uint32_t m_outputHandle = 0; + + struct { + SP lockSurface; + bool configured = false; + } m_lockSurfaceState; + + friend class CWaylandSessionLockState; + }; +}; diff --git a/src/window/Window.hpp b/src/window/Window.hpp index 22d19ee..a25c607 100644 --- a/src/window/Window.hpp +++ b/src/window/Window.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "../helpers/Memory.hpp" @@ -10,8 +11,9 @@ namespace Hyprtoolkit { std::optional preferredSize; std::optional minSize; std::optional maxSize; - std::string title = "Hyprtoolkit App"; - std::string class_ = "hyprtoolkit-app"; + std::string title = "Hyprtoolkit App"; + std::string class_ = "hyprtoolkit-app"; + uint32_t prefferedOutputId = 0; // popups Hyprutils::Math::Vector2D pos; diff --git a/tests/SimpleSessionLock.cpp b/tests/SimpleSessionLock.cpp new file mode 100644 index 0000000..b7c4fad --- /dev/null +++ b/tests/SimpleSessionLock.cpp @@ -0,0 +1,154 @@ +#include "hyprtoolkit/core/SessionLock.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; +using namespace Hyprtoolkit; + +#define SP CSharedPointer +#define WP CWeakPointer +#define UP CUniquePointer + +static SP backend; +static SP lockState; +static std::vector> windows; + +static void addTimer(SP rect) { + backend->addTimer( + std::chrono::seconds(1), + [rect](Hyprutils::Memory::CAtomicSharedPointer timer, void* data) { + rect->rebuild()->color([] { return CHyprColor{rand() % 1000 / 1000.F, rand() % 1000 / 1000.F, rand() % 1000 / 1000.F, 1.F}; })->commence(); + + addTimer(rect); + }, + nullptr); +} + +static void layout(const SP& window) { + window->m_rootElement->addChild(CRectangleBuilder::begin()->color([] { return CHyprColor{0.1F, 0.1F, 0.1F}; })->commence()); + + auto layout = CRowLayoutBuilder::begin()->commence(); + + window->m_rootElement->addChild(layout); + + auto rect3 = CRectangleBuilder::begin() // + ->color([] { return CHyprColor{0.2F, 0.4F, 0.4F}; }) + ->rounding(10) + ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {150, 150}}) + ->commence(); + + auto rect4 = CRectangleBuilder::begin() // + ->color([] { return CHyprColor{0.4F, 0.2F, 0.4F}; }) + ->rounding(0) + ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {50, 50}}) + ->commence(); + + auto layout2 = CColumnLayoutBuilder::begin()->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5F, 1.F}})->commence(); + + auto image = CImageBuilder::begin() // + ->path("/home/max/media/picture/avatar.png") + ->size({CDynamicSize::HT_SIZE_ABSOLUTE, CDynamicSize::HT_SIZE_ABSOLUTE, {447, 447}}) + ->commence(); + + auto text = CTextBuilder::begin()->text("never give up")->color([] { return CHyprColor{0.4F, 0.4F, 0.4F}; })->commence(); + + auto button = CButtonBuilder::begin() + ->label("Click to unlock") + ->onMainClick([](SP el) { + el->rebuild()->label("Unlocking...")->commence(); + lockState->unlock(); + }) + ->onRightClick([](SP el) { el->rebuild()->label("Reset")->commence(); }) + ->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_AUTO, {1, 1}}) + ->commence(); + + text->setGrow(true); + rect4->setGrow(true); + + addTimer(rect4); + + layout2->addChild(image); + layout2->addChild(button); + layout2->addChild(text); + layout->addChild(layout2); + layout->addChild(rect3); + layout->addChild(rect4); + + window->open(); +} + +static void closeWindow(WP window) { + std::println("Remove surface {}!", (uintptr_t)windows.back().get()); + auto windowsIt = std::ranges::find_if(windows, [&window](const auto& w) { return w.get() == window.get(); }); + if (windowsIt == windows.end()) + return; + + (*windowsIt)->close(); + windows.erase(windowsIt); + if (windows.empty()) { + std::println("It's over!!!"); + backend->destroy(); + } +} + +static void createLockSurface(SP output) { + windows.emplace_back(CWindowBuilder::begin()->type(HT_WINDOW_LOCK_SURFACE)->prefferedOutput(output)->commence()); + std::println("New surface {}!", (uintptr_t)windows.back().get()); + WP weakWindow = windows.back(); + weakWindow->m_events.closeRequest.listenStatic([weakWindow]() { closeWindow(weakWindow); }); + + layout(weakWindow.lock()); +} + +int main(int argc, char** argv, char** envp) { + int unlockSecs = 10; + if (argc == 2) + unlockSecs = atoi(argv[1]); + + backend = IBackend::create(); + if (!backend) { + std::println("Backend create failed!"); + return 1; + } + + auto sessionLockState = backend->aquireSessionLock(); + if (sessionLockState.has_value()) + lockState = sessionLockState.value(); + + if (!lockState) { + std::println("Cloudn't lock"); + return 1; + } + + lockState->m_events.finished.listenStatic([] { + std::println("Compositor kicked us"); + for (const auto& w : windows) { + closeWindow(w); + } + }); + + backend->m_events.outputAdded.listenStatic(createLockSurface); + + for (const auto& o : backend->getOutputs()) { + createLockSurface(o); + } + + backend->addTimer(std::chrono::seconds(unlockSecs), [](auto, auto) { lockState->unlock(); }, nullptr); + + backend->enterLoop(); + + return 0; +}