From 7d18b4816e85cf82dc9e38a026594528f795c894 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Fri, 2 Sep 2022 18:06:00 +0200 Subject: [PATCH] initial commit --- .gitignore | 23 ++ CMakeLists.txt | 78 ++++++ Makefile | 60 +++++ README.md | 13 + protocols/wlr-layer-shell-unstable-v1.xml | 285 ++++++++++++++++++++ protocols/wlr-screencopy-unstable-v1.xml | 179 +++++++++++++ src/debug/Log.cpp | 60 +++++ src/debug/Log.hpp | 17 ++ src/defines.hpp | 20 ++ src/events/Events.cpp | 173 ++++++++++++ src/events/Events.hpp | 63 +++++ src/helpers/LayerSurface.cpp | 40 +++ src/helpers/LayerSurface.hpp | 36 +++ src/helpers/Monitor.hpp | 15 ++ src/helpers/PoolBuffer.hpp | 18 ++ src/helpers/Vector2D.cpp | 23 ++ src/helpers/Vector2D.hpp | 39 +++ src/hyprpicker.cpp | 308 ++++++++++++++++++++++ src/hyprpicker.hpp | 53 ++++ src/includes.hpp | 46 ++++ src/main.cpp | 10 + 21 files changed, 1559 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 protocols/wlr-layer-shell-unstable-v1.xml create mode 100644 protocols/wlr-screencopy-unstable-v1.xml 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/LayerSurface.cpp create mode 100644 src/helpers/LayerSurface.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/hyprpicker.cpp create mode 100644 src/hyprpicker.hpp create mode 100644 src/includes.hpp create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47c137e --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +build/ +result +/.vscode/ + +*.o +*-protocol.c +*-protocol.h +.ccls-cache + +gmon.out +*.out +*.tar.gz \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f93f2ce --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 3.4) +project(hyprpicker + DESCRIPTION "A blazing fast wayland wallpaper utility" +) + +set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") + +message(STATUS "Configuring hyprpicker!") + +# 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++23 -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-client wayland-protocols cairo pango pangocairo libjpeg) + +file(GLOB_RECURSE SRCFILES "src/*.cpp") + +add_executable(hyprpicker ${SRCFILES}) + +target_compile_definitions(hyprpicker PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"") +target_compile_definitions(hyprpicker PRIVATE "-DGIT_BRANCH=\"${GIT_BRANCH}\"") +target_compile_definitions(hyprpicker PRIVATE "-DGIT_COMMIT_MESSAGE=\"${GIT_COMMIT_MESSAGE}\"") +target_compile_definitions(hyprpicker PRIVATE "-DGIT_DIRTY=\"${GIT_DIRTY}\"") + +target_link_libraries(hyprpicker rt) + +set(CPACK_PROJECT_NAME ${PROJECT_NAME}) +set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) +include(CPack) + +target_link_libraries(hyprpicker PkgConfig::deps) + +target_link_libraries(hyprpicker + OpenGL + GLESv2 + pthread + ${CMAKE_THREAD_LIBS_INIT} + ${CMAKE_SOURCE_DIR}/wlr-layer-shell-unstable-v1-protocol.o + ${CMAKE_SOURCE_DIR}/wlr-screencopy-unstable-v1-protocol.o + ${CMAKE_SOURCE_DIR}/xdg-shell-protocol.o + wayland-cursor +) + +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..7ff7e70 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +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 + +wlr-screencopy-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) client-header \ + protocols/wlr-screencopy-unstable-v1.xml $@ + +wlr-screencopy-unstable-v1-protocol.c: + $(WAYLAND_SCANNER) private-code \ + protocols/wlr-screencopy-unstable-v1.xml $@ + +wlr-screencopy-unstable-v1-protocol.o: wlr-screencopy-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 wlr-screencopy-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 + +all: + make clear + make protocols + make release diff --git a/README.md b/README.md new file mode 100644 index 0000000..046ddd7 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# hyprpicker + +A wlroots-compatible Wayland color picker that does not suck. + +# Usage + +launch it. Click. That's it. + +# Building + +`make all` + +the output binary is in `./build/hyprpicker` \ 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/protocols/wlr-screencopy-unstable-v1.xml b/protocols/wlr-screencopy-unstable-v1.xml new file mode 100644 index 0000000..a7a2d17 --- /dev/null +++ b/protocols/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,179 @@ + + + + Copyright © 2018 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to ask the compositor to copy part of the + screen content to a client buffer. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + Capture the next frame of an entire output. + + + + + + + + + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. The region will be clipped to the output's + extents. + + + + + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single frame. + + When created, a "buffer" event will be sent. The client will then be able + to send a "copy" request. If the capture is successful, the compositor + will send a "flags" followed by a "ready" event. + + If the capture failed, the "failed" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "failed" event is received, the client should + destroy the frame. + + + + + Provides information about the frame's buffer. This event is sent once + as soon as the frame is created. + + The client should then create a buffer with the provided attributes, and + send a "copy" request. + + + + + + + + + + Copy the frame to the supplied buffer. The buffer must have a the + correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to + have a supported format. + + If the frame is successfully copied, a "flags" and a "ready" events are + sent. Otherwise, a "failed" event is sent. + + + + + + + + + + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. This event includes the time at which presentation happened + at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy the object. + + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp new file mode 100644 index 0000000..3f639e0 --- /dev/null +++ b/src/debug/Log.cpp @@ -0,0 +1,60 @@ +#include "Log.hpp" + +#include +#include + +#include "../includes.hpp" + +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..f8516fc --- /dev/null +++ b/src/defines.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "debug/Log.hpp" +#include "helpers/Vector2D.hpp" +#include "includes.hpp" +#include "helpers/Monitor.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..428d9e7 --- /dev/null +++ b/src/events/Events.cpp @@ -0,0 +1,173 @@ +#include "Events.hpp" +#include "../hyprpicker.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->ready = true; +} + +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 = 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 PLAYERSURFACE = (CLayerSurface *)data; + + PLAYERSURFACE->m_pMonitor->size = Vector2D(width, height); + PLAYERSURFACE->ACKSerial = serial; + PLAYERSURFACE->wantsACK = true; + PLAYERSURFACE->working = true; + + g_pHyprpicker->recheckACK(); +} + +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_pHyprpicker->m_pCompositor = (wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + g_pHyprpicker->m_pWLSHM = (wl_shm *)wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + g_pHyprpicker->m_mtTickMutex.lock(); + + const auto PMONITOR = g_pHyprpicker->m_vMonitors.emplace_back(std::make_unique()).get(); + PMONITOR->wayland_name = name; + PMONITOR->name = ""; + PMONITOR->output = (wl_output *)wl_registry_bind(registry, name, &wl_output_interface, 4); + wl_output_add_listener(PMONITOR->output, &Events::outputListener, PMONITOR); + + g_pHyprpicker->m_mtTickMutex.unlock(); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + g_pHyprpicker->m_pLayerShell = (zwlr_layer_shell_v1 *)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + g_pHyprpicker->createSeat((wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, 1)); + } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { + g_pHyprpicker->m_pSCMgr = (zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, 1); + } +} + +void Events::handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name) { + // todo +} + +void Events::handleCapabilities(void *data, wl_seat *wl_seat, uint32_t capabilities) { + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + wl_pointer_add_listener(wl_seat_get_pointer(wl_seat), &pointerListener, wl_seat); + } else { + Debug::log(CRIT, "Hyprpicker cannot work without a pointer!"); + g_pHyprpicker->finish(1); + } +} + +void Events::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) { + for (auto& ls : g_pHyprpicker->m_vLayerSurfaces) { + if (ls->pSurface == surface) { + g_pHyprpicker->m_pLastSurface = ls.get(); + + wl_surface_set_buffer_scale(ls->pCursorSurface, ls->m_pMonitor->scale); + wl_surface_attach(ls->pCursorSurface, wl_cursor_image_get_buffer(ls->pCursorImg), 0, 0); + wl_pointer_set_cursor(wl_pointer, serial, ls->pCursorSurface, ls->pCursorImg->hotspot_x / ls->m_pMonitor->scale, ls->pCursorImg->hotspot_y / ls->m_pMonitor->scale); + wl_surface_commit(ls->pCursorSurface); + } + } +} + +void Events::handlePointerLeave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { + // ignored +} + +void Events::handlePointerAxis(void *data, wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + // ignored +} + +void Events::handlePointerMotion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + + auto x = wl_fixed_to_double(surface_x); + auto y = wl_fixed_to_double(surface_y); + + g_pHyprpicker->m_vLastCoords = {x, y}; + + g_pHyprpicker->markDirty(); +} + +void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) { + // get the px and print it + const auto CLICKPOS = g_pHyprpicker->m_vLastCoords.floor(); + + const auto PLS = g_pHyprpicker->m_pLastSurface; + + struct pixel { + unsigned char blue; + unsigned char green; + unsigned char red; + unsigned char alpha; + } *px = (struct pixel *)(PLS->screenBuffer.data + (int)CLICKPOS.y * (int)PLS->screenBuffer.pixelSize.x * 4 + (int)CLICKPOS.x * 4); + + Debug::log(NONE, "Result RGBA: %i %i %i %i", px->red, px->green, px->blue, px->alpha); + + g_pHyprpicker->finish(); +} + +void Events::handleFrameDone(void *data, struct wl_callback *callback, uint32_t time) { + CLayerSurface* pLS = (CLayerSurface*)data; + + if (pLS->frame_callback) + wl_callback_destroy(pLS->frame_callback); + + pLS->frame_callback = nullptr; + + if (pLS->dirty || !pLS->rendered) + g_pHyprpicker->renderSurface(g_pHyprpicker->m_pLastSurface); +} + +void Events::handleBufferRelease(void *data, struct wl_buffer *wl_buffer) { + auto buf = (SPoolBuffer*)data; + buf->busy = false; +} + +void Events::handleSCBuffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + const auto PLS = (CLayerSurface*)data; + + g_pHyprpicker->createBuffer(&PLS->screenBuffer, width, height, format); + + zwlr_screencopy_frame_v1_copy(frame, PLS->screenBuffer.buffer); +} + +void Events::handleSCFlags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { + const auto PLS = (CLayerSurface *)data; + + PLS->scflags = flags; + + g_pHyprpicker->recheckACK(); + + g_pHyprpicker->renderSurface(PLS); +} + +void Events::handleSCReady(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + // ignore +} + +void Events::handleSCFailed(void *data, struct zwlr_screencopy_frame_v1 *frame) { + Debug::log(CRIT, "Failed to get a Screencopy!"); + g_pHyprpicker->finish(1); +} \ No newline at end of file diff --git a/src/events/Events.hpp b/src/events/Events.hpp new file mode 100644 index 0000000..bbf6a19 --- /dev/null +++ b/src/events/Events.hpp @@ -0,0 +1,63 @@ +#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, wl_registry *registry, uint32_t name, const char *interface, uint32_t version); + + void handleGlobalRemove(void *data, wl_registry *registry, uint32_t name); + + void handleCapabilities(void *data, wl_seat *wl_seat, uint32_t capabilities); + + void handlePointerMotion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); + + void handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state); + + void handlePointerAxis(void *data, wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value); + + 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); + + void handlePointerLeave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface); + + void handleFrameDone(void *data, struct wl_callback *callback, uint32_t time); + + void handleBufferRelease(void *data, struct wl_buffer *wl_buffer); + + void handleSCBuffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride); + + void handleSCFlags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags); + + void handleSCReady(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec); + + void handleSCFailed(void *data, struct zwlr_screencopy_frame_v1 *frame); + + 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 wl_registry_listener registryListener = { .global = handleGlobal, .global_remove = handleGlobalRemove }; + + inline const wl_seat_listener seatListener = { .capabilities = handleCapabilities }; + + inline const wl_pointer_listener pointerListener = { .enter = handlePointerEnter, .leave = handlePointerLeave, .motion = handlePointerMotion, .button = handlePointerButton, .axis = handlePointerAxis }; + + inline const wl_callback_listener frameListener = { .done = handleFrameDone }; + + inline const wl_buffer_listener bufferListener = { .release = handleBufferRelease }; + + inline const zwlr_screencopy_frame_v1_listener screencopyListener = { .buffer = handleSCBuffer, .flags = handleSCFlags, .ready = handleSCReady, .failed = handleSCFailed }; +}; \ No newline at end of file diff --git a/src/helpers/LayerSurface.cpp b/src/helpers/LayerSurface.cpp new file mode 100644 index 0000000..dc2b4bc --- /dev/null +++ b/src/helpers/LayerSurface.cpp @@ -0,0 +1,40 @@ +#include "LayerSurface.hpp" + +#include "../hyprpicker.hpp" +#include "../events/Events.hpp" + +CLayerSurface::CLayerSurface(SMonitor* pMonitor) { + m_pMonitor = pMonitor; + + pSurface = wl_compositor_create_surface(g_pHyprpicker->m_pCompositor); + + if (!pSurface) { + Debug::log(CRIT, "The compositor did not allow hyprpicker a surface!"); + g_pHyprpicker->finish(1); + return; + } + + pLayerSurface = zwlr_layer_shell_v1_get_layer_surface(g_pHyprpicker->m_pLayerShell, pSurface, pMonitor->output, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "hyprpicker"); + + if (!pLayerSurface) { + Debug::log(CRIT, "The compositor did not allow hyprpicker a layersurface!"); + g_pHyprpicker->finish(1); + return; + } + + zwlr_layer_surface_v1_set_size(pLayerSurface, 0, 0); + zwlr_layer_surface_v1_set_anchor(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(pLayerSurface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(pLayerSurface, true); + zwlr_layer_surface_v1_add_listener(pLayerSurface, &Events::layersurfaceListener, this); + wl_surface_commit(pSurface); + + wl_display_flush(g_pHyprpicker->m_pWLDisplay); +} + +CLayerSurface::~CLayerSurface() { + wl_surface_destroy(pSurface); + zwlr_layer_surface_v1_destroy(pLayerSurface); + + wl_display_flush(g_pHyprpicker->m_pWLDisplay); +} \ No newline at end of file diff --git a/src/helpers/LayerSurface.hpp b/src/helpers/LayerSurface.hpp new file mode 100644 index 0000000..6a86c87 --- /dev/null +++ b/src/helpers/LayerSurface.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "../defines.hpp" +#include "PoolBuffer.hpp" + +struct SMonitor; + +class CLayerSurface { +public: + CLayerSurface(SMonitor*); + ~CLayerSurface(); + + SMonitor* m_pMonitor = nullptr; + + zwlr_layer_surface_v1* pLayerSurface = nullptr; + wl_surface* pSurface = nullptr; + wl_surface* pCursorSurface = nullptr; + + bool wantsACK = false; + uint32_t ACKSerial = 0; + bool working = false; + + int lastBuffer = 0; + SPoolBuffer buffers[2]; + + SPoolBuffer screenBuffer; + uint32_t scflags = 0; + + bool dirty = true; + + bool rendered = false; + + wl_callback* frame_callback = nullptr; + + wl_cursor_image* pCursorImg = nullptr; +}; \ No newline at end of file diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp new file mode 100644 index 0000000..48583f2 --- /dev/null +++ b/src/helpers/Monitor.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "../defines.hpp" + +struct SMonitor { + std::string name = ""; + wl_output* output = nullptr; + uint32_t wayland_name = 0; + Vector2D size; + int scale; + + bool ready = false; + + zwlr_screencopy_frame_v1* pSCFrame = nullptr; +}; \ No newline at end of file diff --git a/src/helpers/PoolBuffer.hpp b/src/helpers/PoolBuffer.hpp new file mode 100644 index 0000000..bf2db3e --- /dev/null +++ b/src/helpers/PoolBuffer.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "../defines.hpp" + +struct SPoolBuffer { + wl_buffer* buffer = nullptr; + cairo_surface_t* surface = nullptr; + cairo_t* cairo = nullptr; + void* data = nullptr; + size_t size = 0; + Vector2D pixelSize; + + uint32_t format; + + std::string name; + + bool busy = false; +}; \ 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/hyprpicker.cpp b/src/hyprpicker.cpp new file mode 100644 index 0000000..6590a4c --- /dev/null +++ b/src/hyprpicker.cpp @@ -0,0 +1,308 @@ +#include "hyprpicker.hpp" +#include "events/Events.hpp" + +void CHyprpicker::init() { + m_pWLDisplay = wl_display_connect(nullptr); + + if (!m_pWLDisplay) { + Debug::log(CRIT, "No wayland compositor running!"); + exit(1); + return; + } + + m_pWLRegistry = wl_display_get_registry(m_pWLDisplay); + + wl_registry_add_listener(m_pWLRegistry, &Events::registryListener, nullptr); + + wl_display_roundtrip(m_pWLDisplay); + + for (auto& m : m_vMonitors) { + m_vLayerSurfaces.emplace_back(std::make_unique(m.get())); + + m_pLastSurface = m_vLayerSurfaces.back().get(); + + m->pSCFrame = zwlr_screencopy_manager_v1_capture_output(m_pSCMgr, false, m->output); + + zwlr_screencopy_frame_v1_add_listener(m->pSCFrame, &Events::screencopyListener, m_pLastSurface); + + m_pLastSurface->pCursorSurface = wl_compositor_create_surface(m_pCompositor); + } + + wl_display_roundtrip(m_pWLDisplay); + + while (m_bRunning && wl_display_dispatch(m_pWLDisplay) != -1) { + //renderSurface(m_pLastSurface); + } +} + +void CHyprpicker::finish(int code) { + for (auto& ls : m_vLayerSurfaces) { + destroyBuffer(&ls->buffers[0]); + destroyBuffer(&ls->buffers[1]); + destroyBuffer(&ls->screenBuffer); + } + + exit(code); +} + +void CHyprpicker::recheckACK() { + for (auto& ls : m_vLayerSurfaces) { + if (ls->wantsACK && ls->screenBuffer.buffer) { + ls->wantsACK = false; + zwlr_layer_surface_v1_ack_configure(ls->pLayerSurface, ls->ACKSerial); + + if (!ls->buffers[0].buffer) { + createBuffer(&ls->buffers[0], ls->m_pMonitor->size.x * ls->m_pMonitor->scale, ls->m_pMonitor->size.y * ls->m_pMonitor->scale, WL_SHM_FORMAT_ARGB8888); + createBuffer(&ls->buffers[1], ls->m_pMonitor->size.x * ls->m_pMonitor->scale, ls->m_pMonitor->size.y * ls->m_pMonitor->scale, WL_SHM_FORMAT_ARGB8888); + + ls->pCursorImg = wl_cursor_theme_get_cursor(wl_cursor_theme_load(getenv("XCURSOR_THEME"), std::stoi(getenv("XCURSOR_SIZE")) * ls->m_pMonitor->scale, m_pWLSHM), "left_ptr")->images[0]; + } + } + } + + markDirty(); +} + +void CHyprpicker::markDirty() { + for (auto& ls : m_vLayerSurfaces) { + if (ls->frame_callback) + continue; + + ls->frame_callback = wl_surface_frame(ls->pSurface); + wl_callback_add_listener(ls->frame_callback, &Events::frameListener, ls.get()); + wl_surface_commit(ls->pSurface); + + ls->dirty = true; + } +} + +SPoolBuffer* CHyprpicker::getBufferForLS(CLayerSurface* pLS) { + SPoolBuffer* returns = nullptr; + + for (auto i = 0; i < 2; ++i) { + if (pLS->buffers[i].busy) + continue; + + returns = &pLS->buffers[i]; + } + + if (!returns) + return nullptr; + + returns->busy = true; + + return returns; +} + +bool CHyprpicker::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 CHyprpicker::createPoolFile(size_t size, std::string& name) { + const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR"); + if (!XDGRUNTIMEDIR) { + Debug::log(CRIT, "XDG_RUNTIME_DIR not set!"); + g_pHyprpicker->finish(1); + } + + name = std::string(XDGRUNTIMEDIR) + "/.hyprpicker_XXXXXX"; + + const auto FD = mkstemp((char*)name.c_str()); + if (FD < 0) { + Debug::log(CRIT, "createPoolFile: fd < 0"); + g_pHyprpicker->finish(1); + } + + if (!setCloexec(FD)) { + close(FD); + Debug::log(CRIT, "createPoolFile: !setCloexec"); + g_pHyprpicker->finish(1); + } + + if (ftruncate(FD, size) < 0) { + close(FD); + Debug::log(CRIT, "createPoolFile: ftruncate < 0"); + g_pHyprpicker->finish(1); + } + + return FD; +} + +void CHyprpicker::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!"); + g_pHyprpicker->finish(1); + } + + const auto DATA = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0); + const auto POOL = wl_shm_create_pool(g_pHyprpicker->m_pWLSHM, FD, SIZE); + pBuffer->buffer = wl_shm_pool_create_buffer(POOL, 0, w, h, STRIDE, format); + + wl_buffer_add_listener(pBuffer->buffer, &Events::bufferListener, pBuffer); + + wl_shm_pool_destroy(POOL); + + close(FD); + + pBuffer->format = format; + pBuffer->size = SIZE; + pBuffer->data = DATA; + pBuffer->pixelSize = Vector2D(w, h); + pBuffer->name = name; +} + +void CHyprpicker::destroyBuffer(SPoolBuffer* pBuffer) { + wl_buffer_destroy(pBuffer->buffer); + cairo_destroy(pBuffer->cairo); + cairo_surface_destroy(pBuffer->surface); + munmap(pBuffer->data, pBuffer->size); + + pBuffer->buffer = nullptr; + pBuffer->cairo = nullptr; + pBuffer->surface = nullptr; + + unlink(pBuffer->name.c_str()); +} + +void CHyprpicker::createSeat(wl_seat* pSeat) { + wl_seat_add_listener(pSeat, &Events::seatListener, pSeat); +} + +void CHyprpicker::convertBuffer(SPoolBuffer* pBuffer) { + switch (pBuffer->format) { + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_XRGB8888: + break; + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XBGR8888: { + uint8_t* data = (uint8_t*)pBuffer->data; + + for (int y = 0; y < pBuffer->pixelSize.y; ++y) { + for (int x = 0; x < pBuffer->pixelSize.x; ++x) { + struct pixel { + // little-endian ARGB + unsigned char blue; + unsigned char green; + unsigned char red; + unsigned char alpha; + }* px = (struct pixel*)(data + y * (int)pBuffer->pixelSize.x * 4 + x * 4); + + std::swap(px->red, px->blue); + } + } + } + break; + default: { + Debug::log(CRIT, "Unsupported format %i", pBuffer->format); + } + g_pHyprpicker->finish(1); + } +} + +void CHyprpicker::renderSurface(CLayerSurface* pSurface) { + const auto PBUFFER = getBufferForLS(pSurface); + + if (!PBUFFER || !pSurface->screenBuffer.buffer) + return; + + if (!pSurface->screenBuffer.surface) { + convertBuffer(&pSurface->screenBuffer); + pSurface->screenBuffer.surface = cairo_image_surface_create_for_data((unsigned char*)pSurface->screenBuffer.data, CAIRO_FORMAT_ARGB32, pSurface->screenBuffer.pixelSize.x, pSurface->screenBuffer.pixelSize.y, pSurface->screenBuffer.pixelSize.x * 4); + } + + PBUFFER->surface = cairo_image_surface_create_for_data((unsigned char*)PBUFFER->data, CAIRO_FORMAT_ARGB32, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y, PBUFFER->pixelSize.x * 4); + PBUFFER->cairo = cairo_create(PBUFFER->surface); + + const auto PCAIRO = PBUFFER->cairo; + + cairo_save(PCAIRO); + + cairo_set_source_rgba(PCAIRO, 0, 0, 0, 0); + cairo_rectangle(PCAIRO, 0, 0, pSurface->m_pMonitor->size.x * pSurface->m_pMonitor->scale, pSurface->m_pMonitor->size.y * pSurface->m_pMonitor->scale); + cairo_fill(PCAIRO); + + cairo_set_source_surface(PCAIRO, pSurface->screenBuffer.surface, 0, 0); + cairo_rectangle(PCAIRO, 0, 0, pSurface->m_pMonitor->size.x * pSurface->m_pMonitor->scale, pSurface->m_pMonitor->size.y * pSurface->m_pMonitor->scale); + cairo_fill(PCAIRO); + + // we draw the preview like this + // + // 200px ZOOM: 10x + // | --------- | + // | | + // | x | 200px + // | | + // | --------- | + // + + cairo_restore(PCAIRO); + cairo_save(PCAIRO); + + cairo_set_source_rgba(PCAIRO, 1.f, 0.4f, 0.4f, 0.8f); + + cairo_scale(PCAIRO, 1, 1); + + cairo_arc(PCAIRO, m_vLastCoords.x, m_vLastCoords.y, 101, 0, 2 * M_PI); + cairo_clip(PCAIRO); + + cairo_fill(PCAIRO); + cairo_paint(PCAIRO); + + cairo_surface_flush(PBUFFER->surface); + + cairo_restore(PCAIRO); + cairo_save(PCAIRO); + + const auto PATTERN = cairo_pattern_create_for_surface(pSurface->screenBuffer.surface); + cairo_pattern_set_filter(PATTERN, CAIRO_FILTER_NEAREST); + cairo_matrix_t matrix; + cairo_matrix_init_identity(&matrix); + cairo_matrix_translate(&matrix, (m_vLastCoords.x) / 1.112f, (m_vLastCoords.y) / 1.112f); // WHAT IS THIS SHIT???? WHY???? + cairo_matrix_scale(&matrix, 0.1f, 0.1f); + cairo_pattern_set_matrix(PATTERN, &matrix); + cairo_set_source(PCAIRO, PATTERN); + cairo_arc(PCAIRO, m_vLastCoords.x, m_vLastCoords.y, 100, 0, 2 * M_PI); + cairo_clip(PCAIRO); + cairo_paint(PCAIRO); + + cairo_surface_flush(PBUFFER->surface); + + cairo_restore(PCAIRO); + + cairo_pattern_destroy(PATTERN); + + sendFrame(pSurface); + cairo_destroy(PCAIRO); + cairo_surface_destroy(PBUFFER->surface); + + PBUFFER->cairo = nullptr; + PBUFFER->surface = nullptr; + + pSurface->rendered = true; +} + +void CHyprpicker::sendFrame(CLayerSurface* pSurface) { + pSurface->frame_callback = wl_surface_frame(pSurface->pSurface); + wl_callback_add_listener(pSurface->frame_callback, &Events::frameListener, pSurface); + + wl_surface_attach(pSurface->pSurface, pSurface->lastBuffer == 0 ? pSurface->buffers[0].buffer : pSurface->buffers[1].buffer, 0, 0); + wl_surface_damage_buffer(pSurface->pSurface, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_commit(pSurface->pSurface); + + pSurface->dirty = false; +} \ No newline at end of file diff --git a/src/hyprpicker.hpp b/src/hyprpicker.hpp new file mode 100644 index 0000000..bdefac4 --- /dev/null +++ b/src/hyprpicker.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "defines.hpp" +#include "helpers/LayerSurface.hpp" +#include "helpers/PoolBuffer.hpp" + +class CHyprpicker { +public: + void init(); + + std::mutex m_mtTickMutex; + + wl_compositor* m_pCompositor; + wl_display* m_pWLDisplay; + wl_registry* m_pWLRegistry; + wl_shm* m_pWLSHM; + zwlr_layer_shell_v1* m_pLayerShell; + zwlr_screencopy_manager_v1* m_pSCMgr; + + bool m_bRunning = true; + + std::vector> m_vMonitors; + std::vector> m_vLayerSurfaces; + + void createSeat(wl_seat*); + + CLayerSurface* m_pLastSurface; + + Vector2D m_vLastCoords; + + void renderSurface(CLayerSurface*); + + void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t); + void destroyBuffer(SPoolBuffer*); + int createPoolFile(size_t, std::string&); + bool setCloexec(const int&); + + void recheckACK(); + + void sendFrame(CLayerSurface*); + + SPoolBuffer* getBufferForLS(CLayerSurface*); + + void convertBuffer(SPoolBuffer*); + + void markDirty(); + + void finish(int code = 0); +private: + +}; + +inline std::unique_ptr g_pHyprpicker; \ No newline at end of file diff --git a/src/includes.hpp b/src/includes.hpp new file mode 100644 index 0000000..79f4d1d --- /dev/null +++ b/src/includes.hpp @@ -0,0 +1,46 @@ +#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 "wlr-screencopy-unstable-v1-protocol.h" +#include "xdg-shell-protocol.h" +#include +#include +} + +#undef class +#undef namespace +#undef static + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..8a9b501 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,10 @@ +#include +#include "hyprpicker.hpp" + + +int main(int argc, char** argv, char** envp) { + g_pHyprpicker = std::make_unique(); + g_pHyprpicker->init(); + + return 0; +} \ No newline at end of file