From 05c4b91f953d62cf003a9d5be84d1c2042644a32 Mon Sep 17 00:00:00 2001
From: vaxerski <43317083+vaxerski@users.noreply.github.com>
Date: Fri, 1 Jul 2022 23:05:58 +0200
Subject: [PATCH] initial code commit
---
CMakeLists.txt | 76 ++++++
Makefile | 45 ++++
protocols/wlr-layer-shell-unstable-v1.xml | 285 ++++++++++++++++++++++
src/Hyprpaper.cpp | 233 ++++++++++++++++++
src/Hyprpaper.hpp | 38 +++
src/config/ConfigManager.cpp | 141 +++++++++++
src/config/ConfigManager.hpp | 22 ++
src/debug/Log.cpp | 60 +++++
src/debug/Log.hpp | 17 ++
src/defines.hpp | 19 ++
src/events/Events.cpp | 65 +++++
src/events/Events.hpp | 29 +++
src/helpers/Monitor.hpp | 20 ++
src/helpers/PoolBuffer.hpp | 11 +
src/helpers/Vector2D.cpp | 23 ++
src/helpers/Vector2D.hpp | 39 +++
src/includes.hpp | 44 ++++
src/main.cpp | 13 +
src/render/WallpaperTarget.cpp | 25 ++
src/render/WallpaperTarget.hpp | 16 ++
20 files changed, 1221 insertions(+)
create mode 100644 CMakeLists.txt
create mode 100644 Makefile
create mode 100644 protocols/wlr-layer-shell-unstable-v1.xml
create mode 100644 src/Hyprpaper.cpp
create mode 100644 src/Hyprpaper.hpp
create mode 100644 src/config/ConfigManager.cpp
create mode 100644 src/config/ConfigManager.hpp
create mode 100644 src/debug/Log.cpp
create mode 100644 src/debug/Log.hpp
create mode 100644 src/defines.hpp
create mode 100644 src/events/Events.cpp
create mode 100644 src/events/Events.hpp
create mode 100644 src/helpers/Monitor.hpp
create mode 100644 src/helpers/PoolBuffer.hpp
create mode 100644 src/helpers/Vector2D.cpp
create mode 100644 src/helpers/Vector2D.hpp
create mode 100644 src/includes.hpp
create mode 100644 src/main.cpp
create mode 100644 src/render/WallpaperTarget.cpp
create mode 100644 src/render/WallpaperTarget.hpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..d482238
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,76 @@
+cmake_minimum_required(VERSION 3.4)
+project(hyprpaper
+ DESCRIPTION "A blazing fast wayland wallpaper utility"
+)
+
+set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
+
+message(STATUS "Configuring hyprpaper!")
+
+# Get git info
+# hash and branch
+execute_process(
+ COMMAND git rev-parse --abbrev-ref HEAD
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_BRANCH
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+execute_process(
+ COMMAND git rev-parse HEAD
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_COMMIT_HASH
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+execute_process(
+ COMMAND bash -c "git show ${GIT_COMMIT_HASH} | head -n 5 | tail -n 1"
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_COMMIT_MESSAGE
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+execute_process(
+ COMMAND bash -c "git diff-index --quiet HEAD -- || echo \"dirty\""
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_DIRTY
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+#
+#
+
+include_directories(.)
+add_compile_options(-std=c++20 -DWLR_USE_UNSTABLE )
+add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing)
+find_package(Threads REQUIRED)
+
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-server wayland-client wayland-cursor wayland-protocols cairo pango pangocairo wlroots libjpeg)
+
+file(GLOB_RECURSE SRCFILES "src/*.cpp")
+
+add_executable(hyprpaper ${SRCFILES})
+
+target_compile_definitions(hyprpaper PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
+target_compile_definitions(hyprpaper PRIVATE "-DGIT_BRANCH=\"${GIT_BRANCH}\"")
+target_compile_definitions(hyprpaper PRIVATE "-DGIT_COMMIT_MESSAGE=\"${GIT_COMMIT_MESSAGE}\"")
+target_compile_definitions(hyprpaper PRIVATE "-DGIT_DIRTY=\"${GIT_DIRTY}\"")
+
+target_link_libraries(hyprpaper rt)
+
+set(CPACK_PROJECT_NAME ${PROJECT_NAME})
+set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
+include(CPack)
+
+target_link_libraries(hyprpaper PkgConfig::deps)
+
+target_link_libraries(hyprpaper
+ OpenGL
+ GLESv2
+ pthread
+ ${CMAKE_THREAD_LIBS_INIT}
+ ${CMAKE_SOURCE_DIR}/wlr-layer-shell-unstable-v1-protocol.o
+ ${CMAKE_SOURCE_DIR}/xdg-shell-protocol.o
+)
+
+IF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg -no-pie -fno-builtin")
+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg -no-pie -fno-builtin")
+ SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg -no-pie -fno-builtin")
+ENDIF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a20ab6f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+PREFIX = /usr/local
+CFLAGS ?= -g -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function -Wno-unused-variable -Wno-unused-result -Wdeclaration-after-statement
+
+CFLAGS += -I. -DWLR_USE_UNSTABLE -std=c99
+
+WAYLAND_PROTOCOLS=$(shell pkg-config --variable=pkgdatadir wayland-protocols)
+WAYLAND_SCANNER=$(shell pkg-config --variable=wayland_scanner wayland-scanner)
+
+PKGS = wlroots wayland-server
+CFLAGS += $(foreach p,$(PKGS),$(shell pkg-config --cflags $(p)))
+LDLIBS += $(foreach p,$(PKGS),$(shell pkg-config --libs $(p)))
+
+wlr-layer-shell-unstable-v1-protocol.h:
+ $(WAYLAND_SCANNER) client-header \
+ protocols/wlr-layer-shell-unstable-v1.xml $@
+
+wlr-layer-shell-unstable-v1-protocol.c:
+ $(WAYLAND_SCANNER) private-code \
+ protocols/wlr-layer-shell-unstable-v1.xml $@
+
+wlr-layer-shell-unstable-v1-protocol.o: wlr-layer-shell-unstable-v1-protocol.h
+
+xdg-shell-protocol.h:
+ $(WAYLAND_SCANNER) client-header \
+ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+
+xdg-shell-protocol.c:
+ $(WAYLAND_SCANNER) private-code \
+ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+
+xdg-shell-protocol.o: xdg-shell-protocol.h
+
+protocols: wlr-layer-shell-unstable-v1-protocol.o xdg-shell-protocol.o
+
+clear:
+ rm -rf build
+ rm -f *.o *-protocol.h *-protocol.c
+
+release:
+ mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja
+ cmake --build ./build --config Release --target all -j 10
+
+debug:
+ mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -H./ -B./build -G Ninja
+ cmake --build ./build --config Debug --target all -j 10
\ No newline at end of file
diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml
new file mode 100644
index 0000000..f29eb87
--- /dev/null
+++ b/protocols/wlr-layer-shell-unstable-v1.xml
@@ -0,0 +1,285 @@
+
+
+
+ Copyright © 2017 Drew DeVault
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+
+
+
+
+ Clients can use this interface to assign the surface_layer role to
+ wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ rendered with a defined z-depth respective to each other. They may also be
+ anchored to the edges and corners of a screen and specify input handling
+ semantics. This interface should be suitable for the implementation of
+ many desktop shell components, and a broad number of other applications
+ that interact with the desktop.
+
+
+
+
+ Create a layer surface for an existing surface. This assigns the role of
+ layer_surface, or raises a protocol error if another role is already
+ assigned.
+
+ Creating a layer surface from a wl_surface which has a buffer attached
+ or committed is a client error, and any attempts by a client to attach
+ or manipulate a buffer prior to the first layer_surface.configure call
+ must also be treated as errors.
+
+ You may pass NULL for output to allow the compositor to decide which
+ output to use. Generally this will be the one that the user most
+ recently interacted with.
+
+ Clients can specify a namespace that defines the purpose of the layer
+ surface.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ These values indicate which layers a surface can be rendered in. They
+ are ordered by z depth, bottom-most first. Traditional shell surfaces
+ will typically be rendered between the bottom and top layers.
+ Fullscreen shell surfaces are typically rendered at the top layer.
+ Multiple surfaces can share a single layer, and ordering within a
+ single layer is undefined.
+
+
+
+
+
+
+
+
+
+
+
+ An interface that may be implemented by a wl_surface, for surfaces that
+ are designed to be rendered as a layer of a stacked desktop-like
+ environment.
+
+ Layer surface state (size, anchor, exclusive zone, margin, interactivity)
+ is double-buffered, and will be applied at the time wl_surface.commit of
+ the corresponding wl_surface is called.
+
+
+
+
+ Sets the size of the surface in surface-local coordinates. The
+ compositor will display the surface centered with respect to its
+ anchors.
+
+ If you pass 0 for either value, the compositor will assign it and
+ inform you of the assignment in the configure event. You must set your
+ anchor to opposite edges in the dimensions you omit; not doing so is a
+ protocol error. Both values are 0 by default.
+
+ Size is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+
+ Requests that the compositor anchor the surface to the specified edges
+ and corners. If two orthoginal edges are specified (e.g. 'top' and
+ 'left'), then the anchor point will be the intersection of the edges
+ (e.g. the top left corner of the output); otherwise the anchor point
+ will be centered on that edge, or in the center if none is specified.
+
+ Anchor is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+ Requests that the compositor avoids occluding an area of the surface
+ with other surfaces. The compositor's use of this information is
+ implementation-dependent - do not assume that this region will not
+ actually be occluded.
+
+ A positive value is only meaningful if the surface is anchored to an
+ edge, rather than a corner. The zone is the number of surface-local
+ coordinates from the edge that are considered exclusive.
+
+ Surfaces that do not wish to have an exclusive zone may instead specify
+ how they should interact with surfaces that do. If set to zero, the
+ surface indicates that it would like to be moved to avoid occluding
+ surfaces with a positive excluzive zone. If set to -1, the surface
+ indicates that it would not like to be moved to accommodate for other
+ surfaces, and the compositor should extend it all the way to the edges
+ it is anchored to.
+
+ For example, a panel might set its exclusive zone to 10, so that
+ maximized shell surfaces are not shown on top of it. A notification
+ might set its exclusive zone to 0, so that it is moved to avoid
+ occluding the panel, but shell surfaces are shown underneath it. A
+ wallpaper or lock screen might set their exclusive zone to -1, so that
+ they stretch below or over the panel.
+
+ The default value is 0.
+
+ Exclusive zone is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+ Requests that the surface be placed some distance away from the anchor
+ point on the output, in surface-local coordinates. Setting this value
+ for edges you are not anchored to has no effect.
+
+ The exclusive zone includes the margin.
+
+ Margin is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+
+
+
+ Set to 1 to request that the seat send keyboard events to this layer
+ surface. For layers below the shell surface layer, the seat will use
+ normal focus semantics. For layers above the shell surface layers, the
+ seat will always give exclusive keyboard focus to the top-most layer
+ which has keyboard interactivity set to true.
+
+ Layer surfaces receive pointer, touch, and tablet events normally. If
+ you do not want to receive them, set the input region on your surface
+ to an empty region.
+
+ Events is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+ This assigns an xdg_popup's parent to this layer_surface. This popup
+ should have been created via xdg_surface::get_popup with the parent set
+ to NULL, and this request must be invoked before committing the popup's
+ initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+
+
+
+
+
+
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+
+
+
+
+
+
+ This request destroys the layer surface.
+
+
+
+
+
+ The configure event asks the client to resize its surface.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ The client is free to dismiss all but the last configure event it
+ received.
+
+ The width and height arguments specify the size of the window in
+ surface-local coordinates.
+
+ The size is a hint, in the sense that the client is free to ignore it if
+ it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+ resize in steps of NxM pixels). If the client picks a smaller size and
+ is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+ surface will be centered on this axis.
+
+ If the width or height arguments are zero, it means the client should
+ decide its own window dimension.
+
+
+
+
+
+
+
+
+ The closed event is sent by the compositor when the surface will no
+ longer be shown. The output may have been destroyed or the user may
+ have asked for it to be removed. Further changes to the surface will be
+ ignored. The client should destroy the resource after receiving this
+ event, and create a new surface if they so choose.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Hyprpaper.cpp b/src/Hyprpaper.cpp
new file mode 100644
index 0000000..cffe02b
--- /dev/null
+++ b/src/Hyprpaper.cpp
@@ -0,0 +1,233 @@
+#include "Hyprpaper.hpp"
+
+CHyprpaper::CHyprpaper() { }
+
+void CHyprpaper::init() {
+ g_pConfigManager = std::make_unique();
+
+ m_sDisplay = (wl_display *)wl_display_connect(NULL);
+
+ if (!m_sDisplay) {
+ Debug::log(CRIT, "No wayland compositor running!");
+ exit(1);
+ return;
+ }
+
+ preloadAllWallpapersFromConfig();
+
+ // run
+ wl_registry *registry = wl_display_get_registry(m_sDisplay);
+ wl_registry_add_listener(registry, &Events::registryListener, nullptr);
+
+ while (wl_display_dispatch(m_sDisplay) != -1) {
+ recheckAllMonitors();
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+}
+
+void CHyprpaper::preloadAllWallpapersFromConfig() {
+ for (auto& wp : g_pConfigManager->m_dRequestedPreloads) {
+ m_mWallpaperTargets[wp] = CWallpaperTarget();
+ m_mWallpaperTargets[wp].create(wp);
+ }
+}
+
+void CHyprpaper::recheckAllMonitors() {
+ for (auto& m : m_vMonitors) {
+ ensureMonitorHasActiveWallpaper(&m);
+
+ if (m.wantsACK) {
+ m.wantsACK = false;
+ zwlr_layer_surface_v1_ack_configure(m.pLayerSurface, m.configureSerial);
+ }
+
+ if (m.wantsReload) {
+ m.wantsReload = false;
+ renderWallpaperForMonitor(&m);
+ }
+ }
+}
+
+void CHyprpaper::ensureMonitorHasActiveWallpaper(SMonitor* pMonitor) {
+ if (!pMonitor->readyForLS)
+ return;
+
+ auto it = m_mMonitorActiveWallpaperTargets.find(pMonitor);
+
+ if (it == m_mMonitorActiveWallpaperTargets.end()) {
+ m_mMonitorActiveWallpaperTargets[pMonitor] = nullptr;
+ it = m_mMonitorActiveWallpaperTargets.find(pMonitor);
+ }
+
+ if (it->second)
+ return; // has
+
+ // create it for thy
+ createLSForMonitor(pMonitor);
+
+ // get the target
+ for (auto&[mon, path1] : m_mMonitorActiveWallpapers) {
+ if (mon == pMonitor->name) {
+ for (auto&[path2, target] : m_mWallpaperTargets) {
+ if (path1 == path2) {
+ it->second = ⌖
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (!it->second) {
+ Debug::log(CRIT, "No target for monitor %s!!", pMonitor->name.c_str());
+ exit(1);
+ return;
+ }
+}
+
+void CHyprpaper::createLSForMonitor(SMonitor* pMonitor) {
+ pMonitor->pSurface = wl_compositor_create_surface(m_sCompositor);
+
+ if (!pMonitor->pSurface) {
+ Debug::log(CRIT, "The compositor did not allow hyprpaper a surface!");
+ exit(1);
+ return;
+ }
+
+ const auto PINPUTREGION = wl_compositor_create_region(m_sCompositor);
+
+ if (!PINPUTREGION) {
+ Debug::log(CRIT, "The compositor did not allow hyprpaper a region!");
+ exit(1);
+ return;
+ }
+
+ wl_surface_set_input_region(pMonitor->pSurface, PINPUTREGION);
+
+ pMonitor->pLayerSurface = zwlr_layer_shell_v1_get_layer_surface(g_pHyprpaper->m_sLayerShell, pMonitor->pSurface, pMonitor->output, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "hyprpaper");
+
+ if (!pMonitor->pLayerSurface) {
+ Debug::log(CRIT, "The compositor did not allow hyprpaper a layersurface!");
+ exit(1);
+ return;
+ }
+
+ zwlr_layer_surface_v1_set_size(pMonitor->pLayerSurface, 0, 0);
+ zwlr_layer_surface_v1_set_anchor(pMonitor->pLayerSurface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
+ zwlr_layer_surface_v1_set_exclusive_zone(pMonitor->pLayerSurface, -1);
+ zwlr_layer_surface_v1_add_listener(pMonitor->pLayerSurface, &Events::layersurfaceListener, pMonitor);
+ wl_surface_commit(pMonitor->pSurface);
+
+ wl_region_destroy(PINPUTREGION);
+}
+
+bool CHyprpaper::setCloexec(const int& FD) {
+ long flags = fcntl(FD, F_GETFD);
+ if (flags == -1) {
+ return false;
+ }
+
+ if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) {
+ return false;
+ }
+
+ return true;
+}
+
+int CHyprpaper::createPoolFile(size_t size, std::string& name) {
+ const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR");
+ if (!XDGRUNTIMEDIR) {
+ Debug::log(CRIT, "XDG_RUNTIME_DIR not set!");
+ exit(1);
+ }
+
+ name = std::string(XDGRUNTIMEDIR) + "/.hyprpaper_XXXXXX";
+
+ const auto FD = mkstemp((char*)name.c_str());
+ if (FD < 0) {
+ Debug::log(CRIT, "createPoolFile: fd < 0");
+ exit(1);
+ }
+
+ if (!setCloexec(FD)) {
+ close(FD);
+ Debug::log(CRIT, "createPoolFile: !setCloexec");
+ exit(1);
+ }
+
+ if (ftruncate(FD, size) < 0) {
+ close(FD);
+ Debug::log(CRIT, "createPoolFile: ftruncate < 0");
+ exit(1);
+ }
+
+ return FD;
+}
+
+void CHyprpaper::createBuffer(SPoolBuffer* pBuffer, int32_t w, int32_t h, uint32_t format) {
+ const uint STRIDE = w * 4;
+ const size_t SIZE = STRIDE * h;
+
+ std::string name;
+ const auto FD = createPoolFile(SIZE, name);
+
+ if (FD == -1) {
+ Debug::log(CRIT, "Unable to create pool file!");
+ exit(1);
+ }
+
+ const auto DATA = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
+ const auto POOL = wl_shm_create_pool(g_pHyprpaper->m_sSHM, FD, SIZE);
+ pBuffer->buffer = wl_shm_pool_create_buffer(POOL, 0, w, h, STRIDE, format);
+ wl_shm_pool_destroy(POOL);
+
+ close(FD);
+
+ pBuffer->size = SIZE;
+ pBuffer->data = DATA;
+ pBuffer->surface = cairo_image_surface_create_for_data((unsigned char*)DATA, CAIRO_FORMAT_ARGB32, w, h, STRIDE);
+ pBuffer->cairo = cairo_create(pBuffer->surface);
+}
+
+void CHyprpaper::destroyBuffer(SPoolBuffer* pBuffer) {
+ wl_buffer_destroy(pBuffer->buffer);
+ cairo_destroy(pBuffer->cairo);
+ cairo_surface_destroy(pBuffer->surface);
+ munmap(pBuffer->data, pBuffer->size);
+}
+
+void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) {
+ SPoolBuffer buffer;
+ createBuffer(&buffer, pMonitor->size.x * pMonitor->scale, pMonitor->size.y * pMonitor->scale, WL_SHM_FORMAT_ARGB8888);
+
+ const auto PCAIRO = buffer.cairo;
+ cairo_save(PCAIRO);
+ cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR);
+ cairo_paint(PCAIRO);
+ cairo_restore(PCAIRO);
+
+ // render
+ // get wp
+ const auto PWALLPAPERTARGET = m_mMonitorActiveWallpaperTargets[pMonitor];
+
+ if (!PWALLPAPERTARGET) {
+ Debug::log(CRIT, "wallpaper target null in render??");
+ exit(1);
+ }
+
+ Debug::log(LOG, "Scale: %i", pMonitor->scale);
+ cairo_scale(PCAIRO, pMonitor->scale, pMonitor->scale);
+ cairo_set_source_surface(PCAIRO, PWALLPAPERTARGET->m_pCairoSurface, 0, 0);
+
+ cairo_paint(PCAIRO);
+ cairo_restore(PCAIRO);
+
+ wl_surface_set_buffer_scale(pMonitor->pSurface, pMonitor->scale);
+ wl_surface_attach(pMonitor->pSurface, buffer.buffer, 0, 0);
+ wl_surface_damage_buffer(pMonitor->pSurface, 0, 0, INT32_MAX, INT32_MAX);
+ wl_surface_commit(pMonitor->pSurface);
+
+ // we will not reuse the buffer, so destroy it immediately
+ destroyBuffer(&buffer);
+}
\ No newline at end of file
diff --git a/src/Hyprpaper.hpp b/src/Hyprpaper.hpp
new file mode 100644
index 0000000..70ffa06
--- /dev/null
+++ b/src/Hyprpaper.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "defines.hpp"
+#include "config/ConfigManager.hpp"
+#include "render/WallpaperTarget.hpp"
+#include "helpers/Monitor.hpp"
+#include "events/Events.hpp"
+#include "helpers/PoolBuffer.hpp"
+
+class CHyprpaper {
+public:
+ // important
+ wl_display* m_sDisplay;
+ wl_compositor* m_sCompositor;
+ wl_shm* m_sSHM;
+ zwlr_layer_shell_v1* m_sLayerShell;
+
+ // init the utility
+ CHyprpaper();
+ void init();
+
+ std::unordered_map m_mWallpaperTargets;
+ std::unordered_map m_mMonitorActiveWallpapers;
+ std::unordered_map m_mMonitorActiveWallpaperTargets;
+ std::vector m_vMonitors;
+
+ void preloadAllWallpapersFromConfig();
+ void recheckAllMonitors();
+ void ensureMonitorHasActiveWallpaper(SMonitor*);
+ void createLSForMonitor(SMonitor*);
+ void renderWallpaperForMonitor(SMonitor*);
+ void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t);
+ void destroyBuffer(SPoolBuffer*);
+ int createPoolFile(size_t, std::string&);
+ bool setCloexec(const int&);
+};
+
+inline std::unique_ptr g_pHyprpaper;
\ No newline at end of file
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
new file mode 100644
index 0000000..cfbdeff
--- /dev/null
+++ b/src/config/ConfigManager.cpp
@@ -0,0 +1,141 @@
+#include "ConfigManager.hpp"
+#include "../Hyprpaper.hpp"
+
+CConfigManager::CConfigManager() {
+ // init the entire thing
+
+ const char* const ENVHOME = getenv("HOME");
+ const std::string CONFIGPATH = ENVHOME + (std::string) "/.config/hypr/hyprpaper.conf";
+
+ std::ifstream ifs;
+ ifs.open(CONFIGPATH);
+
+ if (!ifs.good()) {
+ Debug::log(CRIT, "Hyprpaper was not provided a config!");
+ exit(1);
+ return; //jic
+ }
+
+ std::string line = "";
+ int linenum = 1;
+ if (ifs.is_open()) {
+ while (std::getline(ifs, line)) {
+ // Read line by line.
+ try {
+ parseLine(line);
+ } catch (...) {
+ Debug::log(ERR, "Error reading line from config. Line:");
+ Debug::log(NONE, "%s", line.c_str());
+
+ parseError += "Config error at line " + std::to_string(linenum) + ": Line parsing error.";
+ }
+
+ if (parseError != "" && parseError.find("Config error at line") != 0) {
+ parseError = "Config error at line " + std::to_string(linenum) + ": " + parseError;
+ }
+
+ ++linenum;
+ }
+
+ ifs.close();
+ }
+
+ if (parseError != "") {
+ Debug::log(CRIT, "Exiting because of config parse errors!\n%s", parseError.c_str());
+ exit(1);
+ return;
+ }
+}
+
+std::string CConfigManager::removeBeginEndSpacesTabs(std::string str) {
+ while (str[0] == ' ' || str[0] == '\t') {
+ str = str.substr(1);
+ }
+
+ while (str.length() != 0 && (str[str.length() - 1] == ' ' || str[str.length() - 1] == '\t')) {
+ str = str.substr(0, str.length() - 1);
+ }
+
+ return str;
+}
+
+void CConfigManager::parseLine(std::string& line) {
+ // first check if its not a comment
+ const auto COMMENTSTART = line.find_first_of('#');
+ if (COMMENTSTART == 0)
+ return;
+
+ // now, cut the comment off
+ if (COMMENTSTART != std::string::npos)
+ line = line.substr(0, COMMENTSTART);
+
+ // remove shit at the beginning
+ while (line[0] == ' ' || line[0] == '\t') {
+ line = line.substr(1);
+ }
+
+ // And parse
+ // check if command
+ const auto EQUALSPLACE = line.find_first_of('=');
+
+ if (EQUALSPLACE == std::string::npos)
+ return;
+
+ const auto COMMAND = removeBeginEndSpacesTabs(line.substr(0, EQUALSPLACE));
+ const auto VALUE = removeBeginEndSpacesTabs(line.substr(EQUALSPLACE + 1));
+ //
+
+ parseKeyword(COMMAND, VALUE);
+}
+
+void CConfigManager::parseKeyword(const std::string& COMMAND, const std::string& VALUE) {
+ if (COMMAND == "wallpaper")
+ handleWallpaper(COMMAND, VALUE);
+ else if (COMMAND == "preload")
+ handlePreload(COMMAND, VALUE);
+ else
+ parseError = "unknown keyword " + COMMAND;
+}
+
+void CConfigManager::handleWallpaper(const std::string& COMMAND, const std::string& VALUE) {
+ if (VALUE.find_first_of(',') == std::string::npos) {
+ parseError = "wallpaper failed (syntax)";
+ return;
+ }
+
+ auto MONITOR = VALUE.substr(0, VALUE.find_first_of(','));
+ auto WALLPAPER = VALUE.substr(VALUE.find_first_of(',') + 1);
+
+ if (WALLPAPER[0] == '~') {
+ static const char* const ENVHOME = getenv("HOME");
+ WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
+ }
+
+ if (!std::filesystem::exists(WALLPAPER)) {
+ parseError = "wallpaper failed (no such file)";
+ return;
+ }
+
+ if (std::find(m_dRequestedPreloads.begin(), m_dRequestedPreloads.end(), WALLPAPER) == m_dRequestedPreloads.end()) {
+ parseError = "wallpaper failed (not preloaded)";
+ return;
+ }
+
+ g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR] = WALLPAPER;
+}
+
+void CConfigManager::handlePreload(const std::string& COMMAND, const std::string& VALUE) {
+ auto WALLPAPER = VALUE;
+
+ if (WALLPAPER[0] == '~') {
+ static const char* const ENVHOME = getenv("HOME");
+ WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
+ }
+
+ if (!std::filesystem::exists(WALLPAPER)) {
+ parseError = "preload failed (no such file)";
+ return;
+ }
+
+ m_dRequestedPreloads.emplace_back(WALLPAPER);
+}
diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp
new file mode 100644
index 0000000..c0d0590
--- /dev/null
+++ b/src/config/ConfigManager.hpp
@@ -0,0 +1,22 @@
+#pragma once
+#include "../defines.hpp"
+
+class CConfigManager {
+public:
+ // gets all the data from the config
+ CConfigManager();
+
+ std::deque m_dRequestedPreloads;
+
+private:
+ std::string parseError = "";
+
+ void parseLine(std::string&);
+ std::string removeBeginEndSpacesTabs(std::string in);
+ void parseKeyword(const std::string&, const std::string&);
+
+ void handleWallpaper(const std::string&, const std::string&);
+ void handlePreload(const std::string&, const std::string&);
+};
+
+inline std::unique_ptr g_pConfigManager;
\ No newline at end of file
diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp
new file mode 100644
index 0000000..ff145d4
--- /dev/null
+++ b/src/debug/Log.cpp
@@ -0,0 +1,60 @@
+#include "Log.hpp"
+#include "../includes.hpp"
+
+#include
+#include
+
+void Debug::log(LogLevel level, const char* fmt, ...) {
+
+ std::string levelstr = "";
+
+ switch (level) {
+ case LOG:
+ levelstr = "[LOG] ";
+ break;
+ case WARN:
+ levelstr = "[WARN] ";
+ break;
+ case ERR:
+ levelstr = "[ERR] ";
+ break;
+ case CRIT:
+ levelstr = "[CRITICAL] ";
+ break;
+ case INFO:
+ levelstr = "[INFO] ";
+ break;
+ default:
+ break;
+ }
+
+ char buf[LOGMESSAGESIZE] = "";
+ char* outputStr;
+ int logLen;
+
+ va_list args;
+ va_start(args, fmt);
+ logLen = vsnprintf(buf, sizeof buf, fmt, args);
+ va_end(args);
+
+ if ((long unsigned int)logLen < sizeof buf) {
+ outputStr = strdup(buf);
+ } else {
+ outputStr = (char*)malloc(logLen + 1);
+
+ if (!outputStr) {
+ printf("CRITICAL: Cannot alloc size %d for log! (Out of memory?)", logLen + 1);
+ return;
+ }
+
+ va_start(args, fmt);
+ vsnprintf(outputStr, logLen + 1U, fmt, args);
+ va_end(args);
+ }
+
+ // hyprpaper only logs to stdout
+ std::cout << levelstr << outputStr << "\n";
+
+ // free the log
+ free(outputStr);
+}
diff --git a/src/debug/Log.hpp b/src/debug/Log.hpp
new file mode 100644
index 0000000..8948086
--- /dev/null
+++ b/src/debug/Log.hpp
@@ -0,0 +1,17 @@
+#pragma once
+#include
+
+#define LOGMESSAGESIZE 1024
+
+enum LogLevel {
+ NONE = -1,
+ LOG = 0,
+ WARN,
+ ERR,
+ CRIT,
+ INFO
+};
+
+namespace Debug {
+ void log(LogLevel level, const char* fmt, ...);
+};
\ No newline at end of file
diff --git a/src/defines.hpp b/src/defines.hpp
new file mode 100644
index 0000000..738148f
--- /dev/null
+++ b/src/defines.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "includes.hpp"
+#include "debug/Log.hpp"
+#include "helpers/Vector2D.hpp"
+
+// git stuff
+#ifndef GIT_COMMIT_HASH
+#define GIT_COMMIT_HASH "?"
+#endif
+#ifndef GIT_BRANCH
+#define GIT_BRANCH "?"
+#endif
+#ifndef GIT_COMMIT_MESSAGE
+#define GIT_COMMIT_MESSAGE "?"
+#endif
+#ifndef GIT_DIRTY
+#define GIT_DIRTY "?"
+#endif
\ No newline at end of file
diff --git a/src/events/Events.cpp b/src/events/Events.cpp
new file mode 100644
index 0000000..5b28bdb
--- /dev/null
+++ b/src/events/Events.cpp
@@ -0,0 +1,65 @@
+#include "Events.hpp"
+#include "../Hyprpaper.hpp"
+
+void Events::geometry(void *data, wl_output *output, int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel, const char *make, const char *model, int32_t transform) {
+ // ignored
+}
+
+void Events::mode(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
+ // ignored
+}
+
+void Events::done(void *data, wl_output *wl_output) {
+ const auto PMONITOR = (SMonitor*)data;
+
+ PMONITOR->readyForLS = true;
+
+ g_pHyprpaper->recheckAllMonitors();
+}
+
+void Events::scale(void *data, wl_output *wl_output, int32_t scale) {
+ const auto PMONITOR = (SMonitor*)data;
+
+ PMONITOR->scale = scale;
+}
+
+void Events::name(void *data, wl_output *wl_output, const char *name) {
+ const auto PMONITOR = (SMonitor*)data;
+
+ PMONITOR->name = std::string(name);
+}
+
+void Events::description(void *data, wl_output *wl_output, const char *description) {
+ // i do not care
+}
+
+void Events::ls_configure(void *data, zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) {
+ const auto PMONITOR = (SMonitor *)data;
+
+ PMONITOR->size = Vector2D(width, height);
+ PMONITOR->wantsReload = true;
+ PMONITOR->configureSerial = serial;
+ PMONITOR->wantsACK = true;
+
+ Debug::log(LOG, "configure for %s", PMONITOR->name.c_str());
+}
+
+void Events::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ g_pHyprpaper->m_sCompositor = (wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, 4);
+ } else if (strcmp(interface, wl_shm_interface.name) == 0) {
+ g_pHyprpaper->m_sSHM = (wl_shm *)wl_registry_bind(registry, name, &wl_shm_interface, 1);
+ } else if (strcmp(interface, wl_output_interface.name) == 0) {
+ const auto PMONITOR = &g_pHyprpaper->m_vMonitors.emplace_back();
+ PMONITOR->wayland_name = name;
+ PMONITOR->output = (wl_output *)wl_registry_bind(registry, name, &wl_output_interface, 4);
+ wl_output_add_listener(PMONITOR->output, &Events::outputListener, PMONITOR);
+ } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
+ g_pHyprpaper->m_sLayerShell = (zwlr_layer_shell_v1 *)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);
+ }
+}
+
+void Events::handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name) {
+ // todo
+}
+
diff --git a/src/events/Events.hpp b/src/events/Events.hpp
new file mode 100644
index 0000000..58f5e09
--- /dev/null
+++ b/src/events/Events.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "../defines.hpp"
+
+namespace Events {
+ void geometry(void *data, wl_output *output, int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel, const char *make, const char *model, int32_t transform);
+
+ void mode(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh);
+
+ void done(void *data, wl_output *wl_output);
+
+ void scale(void *data, wl_output *wl_output, int32_t scale);
+
+ void name(void *data, wl_output *wl_output, const char *name);
+
+ void description(void *data, wl_output *wl_output, const char *description);
+
+ void ls_configure(void *data, zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height);
+
+ void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version);
+
+ void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
+
+ inline const wl_output_listener outputListener = {.geometry = geometry, .mode = mode, .done = done, .scale = scale, .name = name, .description = description};
+
+ inline const zwlr_layer_surface_v1_listener layersurfaceListener = { .configure = ls_configure };
+
+ inline const struct wl_registry_listener registryListener = { .global = handleGlobal, .global_remove = handleGlobalRemove };
+};
\ No newline at end of file
diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp
new file mode 100644
index 0000000..2b0ad81
--- /dev/null
+++ b/src/helpers/Monitor.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "../defines.hpp"
+
+struct SMonitor {
+ std::string name = "";
+ wl_output* output = nullptr;
+ uint32_t wayland_name = 0;
+ Vector2D size;
+ int scale;
+
+ bool readyForLS = false;
+
+ zwlr_layer_surface_v1* pLayerSurface = nullptr;
+ wl_surface* pSurface = nullptr;
+ uint32_t configureSerial = 0;
+
+ bool wantsReload = false;
+ bool wantsACK = false;
+};
\ No newline at end of file
diff --git a/src/helpers/PoolBuffer.hpp b/src/helpers/PoolBuffer.hpp
new file mode 100644
index 0000000..4f1f870
--- /dev/null
+++ b/src/helpers/PoolBuffer.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "../defines.hpp"
+
+struct SPoolBuffer {
+ wl_buffer* buffer;
+ cairo_surface_t *surface;
+ cairo_t *cairo;
+ void* data;
+ size_t size;
+};
\ No newline at end of file
diff --git a/src/helpers/Vector2D.cpp b/src/helpers/Vector2D.cpp
new file mode 100644
index 0000000..acaace1
--- /dev/null
+++ b/src/helpers/Vector2D.cpp
@@ -0,0 +1,23 @@
+#include "Vector2D.hpp"
+
+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 = abs(x) > abs(y) ? abs(x) : abs(y);
+
+ x /= max;
+ y /= max;
+
+ return max;
+}
+
+Vector2D Vector2D::floor() {
+ return Vector2D((int)x, (int)y);
+}
\ No newline at end of file
diff --git a/src/helpers/Vector2D.hpp b/src/helpers/Vector2D.hpp
new file mode 100644
index 0000000..7288a08
--- /dev/null
+++ b/src/helpers/Vector2D.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include
+
+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 float a) const {
+ return Vector2D(this->x * a, this->y * a);
+ }
+ Vector2D operator/(const float 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 floor();
+};
\ No newline at end of file
diff --git a/src/includes.hpp b/src/includes.hpp
new file mode 100644
index 0000000..8bcf789
--- /dev/null
+++ b/src/includes.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#define class _class
+#define namespace _namespace
+#define static
+
+extern "C" {
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+#include "xdg-shell-protocol.h"
+#include
+}
+
+#undef class
+#undef namespace
+#undef static
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..23b3aac
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,13 @@
+#include
+#include "defines.hpp"
+#include "Hyprpaper.hpp"
+
+int main(int argc, char** argv, char** envp) {
+ Debug::log(LOG, "Welcome to hyprpaper!\nbuilt from commit %s (%s)", GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE);
+
+ // starts
+ g_pHyprpaper = std::make_unique();
+ g_pHyprpaper->init();
+
+ return 0;
+}
\ No newline at end of file
diff --git a/src/render/WallpaperTarget.cpp b/src/render/WallpaperTarget.cpp
new file mode 100644
index 0000000..5e3499d
--- /dev/null
+++ b/src/render/WallpaperTarget.cpp
@@ -0,0 +1,25 @@
+#include "WallpaperTarget.hpp"
+
+void CWallpaperTarget::create(const std::string& path) {
+ m_szPath = path;
+
+ cairo_surface_t* CAIROSURFACE = nullptr;
+ if (path.find(".png") == path.length() - 4) {
+ CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
+ } else if (path.find(".jpg") == path.length() - 4 || path.find(".jpeg") == path.length() - 5) {
+ Debug::log(ERR, ".jpg images are not yet supported! :(");
+ exit(1);
+ return;
+ } else {
+ Debug::log(CRIT, "unrecognized image %s", path.c_str());
+ exit(1);
+ return;
+ }
+
+ if (cairo_surface_status(CAIROSURFACE) != CAIRO_STATUS_SUCCESS) {
+ Debug::log(CRIT, "Failed to read image %s because of:\n%s", path.c_str(), cairo_status_to_string(cairo_surface_status(CAIROSURFACE)));
+ exit(1);
+ }
+
+ m_pCairoSurface = CAIROSURFACE;
+}
\ No newline at end of file
diff --git a/src/render/WallpaperTarget.hpp b/src/render/WallpaperTarget.hpp
new file mode 100644
index 0000000..767d890
--- /dev/null
+++ b/src/render/WallpaperTarget.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "../defines.hpp"
+
+class CWallpaperTarget {
+public:
+
+ void create(const std::string& path);
+ void render();
+
+ std::string m_szPath;
+
+ Vector2D m_vSize;
+
+ cairo_surface_t* m_pCairoSurface;
+};
\ No newline at end of file