Compare commits

...

60 commits
v0.1.0 ... main

Author SHA1 Message Date
Vaxry
980ebd486b
version: bump to 0.4.5
Some checks failed
Build Hyprpicker (Nix) / Build (push) Has been cancelled
2025-05-01 01:54:26 +01:00
Honkazel
5dcb341c13
clang-tidy: fix some errors (#118) 2025-04-22 23:24:11 +02:00
nyx
5bbeaeebd3
LayerSurface: always render in callback (#117)
* LayerSurface: always render in callback
2025-04-21 20:47:47 +02:00
Vaxry
fd77aea026 version: bump to 0.4.4 2025-04-11 13:58:11 +01:00
nyx
23664963a1
layerSurface: fix commit lag (#114) 2025-04-09 17:47:02 +02:00
Vaxry
6692091d56 core: buffer handling fixes
fixes #101
2025-03-31 21:27:04 +01:00
vaxerski
09101f77a4 version: bump to 0.4.3 2025-03-24 11:34:15 +00:00
nyx
e990630562
layerSurface: fix circle not following cursor movement (#113)
* layerSurface: fix circle not following cursor movement

* misc: format code
2025-03-21 09:35:34 +01:00
Honkazel
b85b06577d
core: clang-tidy and comp fixes (#110)
* clang-tidy and comp fixes

* remove stupid comment

* oaooaoaoaoao

* quack

* remove comment
2025-02-21 21:32:08 +01:00
Sepandar
36a24e61be
core: Fix negative HSV values (#111)
* fixed description in CMakeLists.txt and fixed negative hsv

* ran clang-format
2025-02-15 18:50:05 +01:00
Mihai Fufezan
c3777320b3
flake.lock: update 2025-01-23 14:33:21 +02:00
vaxerski
0b044884d9 version: bump to 0.4.2 2025-01-17 17:03:08 +01:00
Shreya Gurram
18af93f0ff
feat: adds flag for lowercase hex code (#105)
* feat: adds flag for lowercase hexcodes

* formatting changes
2025-01-17 16:02:10 +00:00
vaxerski
444c40e5e3 core: fix build with hw-s 0.4.4
fixes #102
2024-12-30 19:43:17 +01:00
Austin Horstman
46d2f5a817
flake.nix: gcc13 -> gcc14; flake.lock: update (#99)
* flake.nix: gcc13 -> gcc14

* flake.lock: update
2024-12-16 22:26:55 +01:00
Shreya Gurram
d26cb2f439
core: add a hexcode preview below the cursor (#95) 2024-11-28 16:09:39 +00:00
Vaxry
4e8837ddab
core: minor transform fixes (#94) 2024-11-09 23:02:33 +00:00
Vaxry
89b9352d26 core: don't read negative pixel values in getColorFromPixel 2024-10-24 23:51:22 +01:00
Vaxry
17e1ebe9dc core: avoid out-of-bound reads in getColorFromPixel
fixes #92
2024-10-24 23:50:35 +01:00
TeChn4K
185be7cd73
Nix: rename utillinux to util-linux (#93) 2024-10-24 17:49:02 +03:00
Vaxry
4411a6dc0d README: update options 2024-09-30 22:36:50 +01:00
Vaxry
116cec14a5 version: bump to 0.4.1 2024-09-30 15:05:55 +01:00
Vaxry
3b42f6bee2 core: avoid crash in done callbacks
fixes #89
2024-09-30 15:05:42 +01:00
Vaxry
4d6d01c495 version: bump to 0.4.0 2024-09-29 18:25:12 +01:00
Vaxry
e22a8d437b core: add --version 2024-09-29 18:24:57 +01:00
Vaxry
ed3f644af7 core: implement fractional scaling support
Additionally, adds --quiet, --verbose.
2024-09-29 18:21:47 +01:00
Vaxry
c9238d39f6
core: move to hyprwayland-scanner (#88)
* core: move to hyprwayland-scanner

* Nix: add hw-s, bump flake

* CMake: fix wl-client -> wl-scanner

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2024-09-26 12:58:43 +01:00
svin
38fe668e58
CMake: ensure the manpage location is correct (#86) 2024-08-22 14:26:50 +03:00
Vaxry
cbd963e103 events: use enter coords 2024-08-06 17:10:40 +02:00
Vaxry
2da9a6071e core: minor log cleanups 2024-08-06 17:01:36 +02:00
Mihai Fufezan
c36676ad0c
Nix: makeover 2024-07-18 21:01:51 +03:00
Mihai Fufezan
45032489e2
CMake: fmt 2024-07-18 20:53:14 +03:00
Mihai Fufezan
bf6dface52
CMake, Nix: add VERSION file 2024-07-18 20:52:42 +03:00
Vaxry
cffd2ca3f8 core: migrate to hyprutils for utils 2024-07-17 17:08:10 +02:00
Dylan Laduranty
8791f717ef
core: remove unneeded GLES headers (#82)
Signed-off-by: Dylan Laduranty <dylan.laduranty@mesotic.com>
2024-07-15 16:35:24 +02:00
Dylan Laduranty
653eddcbc3
cmake: remove unneeded dependencies (#81)
Signed-off-by: Dylan Laduranty <dylan.laduranty@mesotic.com>
2024-07-15 15:38:47 +02:00
Corentin Guillevic
b0974381f9 README: add required Debian packages to build 2024-07-02 01:45:34 +03:00
Lennart Klebl
67e0f34e27
core: fix segfault when cursor_shape is not supported (#76)
Co-authored-by: alcubierre-drive <alcubierre-drive>
2024-06-11 17:18:23 +02:00
Vaxry
36d974181d cmake: use CXX_STANDARD instead of a flag
ref #74
2024-06-06 11:49:13 +02:00
Daniel Horton
bff005ac9c
README: Fixed getconf command in build instructions (#72)
getconf NPROCESSORS_CONF isn't a valid command. The correct command is getconf _NPROCESSORS_CONF.
2024-05-14 16:13:12 +01:00
rewine
7862376a40 nix: don't need wlroots as build input 2024-05-11 11:54:20 +03:00
bvr-yr
e2472f499d
output: colorize BG instead of FG (#70) 2024-04-15 16:49:59 +01:00
Félix Dorn
be7a0e82c4
fix: cmyk conversion on pure black (#69) 2024-04-12 00:38:16 +01:00
Mihai Fufezan
0eb49192a5
Nix: override wayland-protocols until merged
Fix include

Ref: https://nixpk.gs/pr-tracker.html?pr=297606
2024-03-24 14:24:36 +02:00
Vaxry
9f05fbdabe core: switch to server-side cursors
ref #51
2024-03-23 20:54:29 +00:00
Mihai Fufezan
0e416939a1
Makefile: remove
No longer used. Functionality has been transfered to CMake.
2024-03-12 22:16:45 +02:00
Mihai Fufezan
698b6ecd54
CMake: add protocol building and install rule
Makefile: delegate protocols to CMakeLists.txt
Nix: update derivation
2024-03-12 21:50:25 +02:00
Evie Ciobanu
2ef703474f fix: nixos build, missing ligblvnd 2023-12-31 03:57:43 +02:00
Vaxry
b6130e3901 core: implement more safety around cursor themes
fixes #38
2023-10-30 16:48:36 +00:00
Vaxry
8a7799ae20 core: support conversions from 10-bit formats
Fixes #16

Authored-by: niki-on-github <arch@local>
2023-10-28 17:31:18 +01:00
TheOnlyMrCat
94010d6b9a
events: Use xkbcommon for keyboard event handling (#43) 2023-10-11 11:24:10 +01:00
vaxerski
7e1765f9f3 internal: fix usage of execlp in clipboard::copy
fixes #42
2023-10-05 11:49:47 +01:00
vaxerski
5ba3268694 internal: use newBuf data for storage of the bitmap 2023-08-07 18:58:44 +02:00
vaxerski
03bc27be94 internal: transform received screencopy buffer 2023-08-04 20:41:30 +02:00
éclairevoyant
0889bd5f6f
use crosshair instead of left pointer as cursor (#37) 2023-06-27 13:33:46 +02:00
vaxerski
1d05cc3423 internal: include sys/types 2023-06-21 13:19:47 +02:00
vaxerski
deaca6a4d8 core: avoid leaving garbage buffers on exit 2023-05-29 19:09:58 +02:00
Mihai Fufezan
0eea88367a
flake.lock: update nixpkgs 2023-05-16 22:27:51 +03:00
Mihai Fufezan
7d78a25fe1
CI: update nix install action 2023-05-03 23:40:19 +03:00
Vaxry
97c5134753
readme: include new options 2023-04-27 14:46:16 +01:00
29 changed files with 1241 additions and 917 deletions

101
.clang-tidy Normal file
View file

@ -0,0 +1,101 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: 'file'
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declaration-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
performance-inefficient-string-concatenation.StrictMode: true
readability-braces-around-statements.ShortStatementLines: 0
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassIgnoredRegexp: I.*
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.EnumPrefix: e
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase

View file

@ -11,15 +11,16 @@ jobs:
with:
submodules: recursive
- name: Install nix
uses: cachix/install-nix-action@v18
uses: cachix/install-nix-action@v20
with:
install_url: https://nixos.org/nix/install
extra_nix_config: |
auto-optimise-store = true
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
experimental-features = nix-command flakes
- uses: cachix/cachix-action@v12
with:
name: hyprland
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build Hyprpicker with default settings
run: nix build --print-build-logs
run: nix build --print-build-logs --accept-flake-config

6
.gitignore vendored
View file

@ -16,6 +16,12 @@ result
*.o
*-protocol.c
*-protocol.h
protocols/*.cpp
protocols/*.hpp
.cache/
.ccls-cache
gmon.out

View file

@ -1,55 +1,123 @@
cmake_minimum_required(VERSION 3.4)
project(hyprpicker
DESCRIPTION "A blazing fast wayland wallpaper utility"
)
cmake_minimum_required(VERSION 3.12)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} VERSION)
project(
hyprpicker
DESCRIPTION "A wlroots-compatible Wayland color picker that does not suck"
VERSION ${VERSION})
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
add_compile_definitions(HYPRPICKER_VERSION="${VERSION}")
message(STATUS "Configuring hyprpicker!")
# Get git info
# hash and branch
# 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)
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)
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)
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)
#
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 -Wno-pointer-arith)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
add_compile_options(-DWLR_USE_UNSTABLE)
add_compile_options(
-Wall
-Wextra
-Wuseless-cast
-Wno-unused-parameter
-Wno-unused-value
-Wno-missing-field-initializers)
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols cairo pango pangocairo libjpeg)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
wayland-client
wayland-protocols
xkbcommon
cairo
pango
pangocairo
libjpeg
hyprutils>=0.2.0
hyprwayland-scanner>=0.4.0)
file(GLOB_RECURSE SRCFILES "src/*.cpp")
add_executable(hyprpicker ${SRCFILES})
target_compile_definitions(hyprpicker PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
pkg_get_variable(WAYLAND_SCANNER_DIR wayland-scanner pkgdatadir)
message(STATUS "Found wayland-scanner at ${WAYLAND_SCANNER_DIR}")
function(protocolnew protoPath protoName external)
if(external)
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
else()
set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
endif()
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp
${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp
COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml
${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprpicker PRIVATE protocols/${protoName}.cpp
protocols/${protoName}.hpp)
endfunction()
function(protocolWayland)
add_custom_command(
OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp
${CMAKE_SOURCE_DIR}/protocols/wayland.hpp
COMMAND hyprwayland-scanner --wayland-enums --client
${WAYLAND_SCANNER_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(hyprpicker PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
endfunction()
protocolwayland()
protocolnew("protocols" "wlr-layer-shell-unstable-v1" true)
protocolnew("protocols" "wlr-screencopy-unstable-v1" true)
protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
protocolnew("staging/fractional-scale" "fractional-scale-v1" false)
protocolnew("stable/viewporter" "viewporter" false)
protocolnew("stable/xdg-shell" "xdg-shell" false)
protocolnew("staging/cursor-shape" "cursor-shape-v1" false)
protocolnew("stable/tablet" "tablet-v2" false)
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_COMMIT_MESSAGE=\"${GIT_COMMIT_MESSAGE}\"")
target_compile_definitions(hyprpicker PRIVATE "-DGIT_DIRTY=\"${GIT_DIRTY}\"")
target_link_libraries(hyprpicker rt)
@ -60,19 +128,21 @@ 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
)
target_link_libraries(hyprpicker pthread ${CMAKE_THREAD_LIBS_INIT}
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)
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)
if(NOT DEFINED CMAKE_INSTALL_MANDIR)
set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
endif()
install(TARGETS hyprpicker)
install(FILES ${CMAKE_SOURCE_DIR}/doc/hyprpicker.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)

View file

@ -1,67 +0,0 @@
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)))
default: all
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
install:
mkdir -p ${DESTDIR}${PREFIX}/bin ${DESTDIR}${PREFIX}/share/man/man1
cp doc/hyprpicker.1 ${DESTDIR}${PREFIX}/share/man/man1
cp build/hyprpicker ${DESTDIR}${PREFIX}/bin

View file

@ -10,23 +10,38 @@ Launch it. Click. That's it.
## Options
`-f | --format=[fmt]` specifies the output format (`cmyk`, `hex`, `rgb`, `hsl`, `hsv`)
`-n | --no-fancy` disables the "fancy" (aka. colored) outputting
`-h | --help` prints a help message
`-a | --autocopy` automatically copies the output to the clipboard (requires [wl-clipboard](https://github.com/bugaevc/wl-clipboard))
See `hyprpicker --help`.
# Building
## Arch
`yay -S hyprpicker-git`
## Manual
`make all`
the output binary is in `./build/hyprpicker`
Install dependencies:
- cmake
- pkg-config
- pango
- cairo
- wayland
- wayland-protocols
- hyprutils
- xkbcommon
Building is done via CMake:
```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
cmake --build ./build --config Release --target hyprpicker -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF`
```
Install with:
```sh
cmake --install ./build
```
# Caveats

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.4.5

View file

@ -1,12 +1,58 @@
{
"nodes": {
"hyprutils": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1737632363,
"narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "006620eb29d54ea9086538891404c78563d1bae1",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"hyprwayland-scanner": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1735493474,
"narHash": "sha256-fktzv4NaqKm94VAkAoVqO/nqQlw+X0/tJJNAeCSfzK4=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "de913476b59ee88685fdc018e77b8f6637a2ae0b",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1662019588,
"narHash": "sha256-oPEjHKGGVbBXqwwL+UjsveJzghWiWV0n9ogo1X6l4cw=",
"lastModified": 1737469691,
"narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2da64a81275b68fdad38af669afeda43d401e94b",
"rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab",
"type": "github"
},
"original": {
@ -18,7 +64,25 @@
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
"hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner",
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
}
},

View file

@ -1,39 +1,63 @@
{
description = "Hyprpicker - a wlroots-compatible Wayland color picker that does not suck";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default-linux";
hyprutils = {
url = "github:hyprwm/hyprutils";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
};
hyprwayland-scanner = {
url = "github:hyprwm/hyprwayland-scanner";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
};
};
outputs = {
self,
nixpkgs,
systems,
...
}: let
} @ inputs: let
inherit (nixpkgs) lib;
genSystems = lib.genAttrs [
# Add more systems if they are supported
"aarch64-linux"
"x86_64-linux"
];
pkgsFor = nixpkgs.legacyPackages;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
localSystem.system = system;
overlays = with self.overlays; [hyprpicker];
});
mkDate = longDate: (lib.concatStringsSep "-" [
(__substring 0 4 longDate)
(__substring 4 2 longDate)
(__substring 6 2 longDate)
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ./VERSION);
in {
overlays.default = _: prev: rec {
hyprpicker = prev.callPackage ./nix/default.nix {
stdenv = prev.gcc12Stdenv;
version = "0.pre" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
inherit (prev.xorg) libXdmcp;
};
hyprpicker-debug = hyprpicker.override {debug = true;};
overlays = {
default = self.overlays.hyprpicker;
hyprpicker = lib.composeManyExtensions [
inputs.hyprutils.overlays.default
inputs.hyprwayland-scanner.overlays.default
(final: prev: {
hyprpicker = prev.callPackage ./nix/default.nix {
stdenv = prev.gcc14Stdenv;
version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
};
hyprpicker-debug = final.hyprpicker.override {debug = true;};
})
];
};
packages = genSystems (system:
(self.overlays.default null pkgsFor.${system})
// {default = self.packages.${system}.hyprpicker;});
packages = eachSystem (system: {
default = self.packages.${system}.hyprpicker;
inherit (pkgsFor.${system}) hyprpicker hyprpicker-debug;
});
formatter = genSystems (system: pkgsFor.${system}.alejandra);
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
};
}

View file

@ -3,87 +3,76 @@
stdenv,
pkg-config,
cmake,
ninja,
cairo,
fribidi,
hyprutils,
hyprwayland-scanner,
libdatrie,
libGL,
libjpeg,
libselinux,
libsepol,
libthai,
libxkbcommon,
pango,
pcre,
utillinux,
pcre2,
util-linux,
wayland,
wayland-protocols,
wayland-scanner,
wlroots,
libXdmcp,
xorg,
debug ? false,
version ? "git",
}:
stdenv.mkDerivation {
pname = "hyprpicker" + lib.optionalString debug "-debug";
inherit version;
src = ../.;
cmakeFlags = lib.optional debug "-DCMAKE_BUILD_TYPE=Debug";
cmakeBuildType =
if debug
then "Debug"
else "Release";
nativeBuildInputs = [
cmake
ninja
hyprwayland-scanner
pkg-config
];
buildInputs = [
cairo
fribidi
hyprutils
libdatrie
libGL
libjpeg
libselinux
libsepol
libthai
libxkbcommon
pango
pcre
pcre2
util-linux
wayland
wayland-protocols
wayland-scanner
wlroots
libXdmcp
utillinux
xorg.libXdmcp
];
configurePhase = ''
runHook preConfigure
make protocols
runHook postConfigure
'';
buildPhase = ''
runHook preBuild
make release
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/{bin,share/licenses}
install -Dm755 build/hyprpicker -t $out/bin
install -Dm644 LICENSE -t $out/share/licenses/hyprpicker
runHook postInstall
'';
outputs = [
"out"
"man"
];
meta = with lib; {
homepage = "https://github.com/hyprwm/hyprpicker";
description = "A wlroots-compatible Wayland color picker that does not suck";
license = licenses.bsd3;
platforms = platforms.linux;
mainProgram = "hyprpicker";
};
}

View file

@ -3,8 +3,8 @@
#include "../includes.hpp"
void Clipboard::copy(const char* fmt, ...) {
char buf[CLIPBOARDMESSAGESIZE] = "";
char* outputStr;
char buf[CLIPBOARDMESSAGESIZE] = "";
char* outputStr;
va_list args;
va_start(args, fmt);
@ -13,7 +13,8 @@ void Clipboard::copy(const char* fmt, ...) {
outputStr = strdup(buf);
execlp("wl-copy", "wl-copy", outputStr, NULL);
if (fork() == 0)
execlp("wl-copy", "wl-copy", outputStr, NULL);
free(outputStr);
}
}

View file

@ -2,35 +2,31 @@
#include <fstream>
#include <iostream>
#include <print>
#include "../includes.hpp"
void Debug::log(LogLevel level, const char* fmt, ...) {
std::string levelstr = "";
if (quiet && (level != ERR && level != CRIT))
return;
if (!verbose && level == TRACE)
return;
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;
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;
char buf[LOGMESSAGESIZE] = "";
char* outputStr;
int logLen;
va_list args;
va_start(args, fmt);
@ -43,7 +39,7 @@ void Debug::log(LogLevel level, const char* fmt, ...) {
outputStr = (char*)malloc(logLen + 1);
if (!outputStr) {
printf("CRITICAL: Cannot alloc size %d for log! (Out of memory?)", logLen + 1);
std::print("CRITICAL: Cannot alloc size {} for log! (Out of memory?)", logLen + 1);
return;
}

View file

@ -5,13 +5,15 @@
enum LogLevel {
NONE = -1,
LOG = 0,
LOG = 0,
WARN,
ERR,
CRIT,
INFO
INFO,
TRACE,
};
namespace Debug {
void log(LogLevel level, const char* fmt, ...);
inline bool quiet = false, verbose = false;
void log(LogLevel level, const char* fmt, ...);
};

View file

@ -1,7 +1,6 @@
#pragma once
#include "debug/Log.hpp"
#include "helpers/Vector2D.hpp"
#include "includes.hpp"
#include "helpers/Monitor.hpp"
#include "helpers/Color.hpp"
@ -19,4 +18,9 @@
#endif
#ifndef GIT_DIRTY
#define GIT_DIRTY "?"
#endif
#endif
#include <sys/types.h>
#include <hyprutils/math/Vector2D.hpp>
using namespace Hyprutils::Math;

View file

@ -1,301 +0,0 @@
#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;
if (name)
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<SMonitor>()).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);
}
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
wl_keyboard_add_listener(wl_seat_get_keyboard(wl_seat), &keyboardListener, wl_seat);
}
}
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) {
g_pHyprpicker->markDirty();
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) {
for (auto& ls : g_pHyprpicker->m_vLayerSurfaces) {
if (ls->pSurface == surface) {
g_pHyprpicker->renderSurface(ls.get(), true);
}
}
}
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) {
auto fmax3 = [](float a, float b, float c) -> float { return (a > b && a > c) ? a : (b > c) ? b : c; };
auto fmin3 = [](float a, float b, float c) -> float { return (a < b && a < c) ? a : (b < c) ? b : c; };
// get the px and print it
const auto SCALE = Vector2D{
g_pHyprpicker->m_pLastSurface->screenBuffer.pixelSize.x / (g_pHyprpicker->m_pLastSurface->buffers[0].pixelSize.x / g_pHyprpicker->m_pLastSurface->m_pMonitor->scale),
g_pHyprpicker->m_pLastSurface->screenBuffer.pixelSize.y / (g_pHyprpicker->m_pLastSurface->buffers[0].pixelSize.y / g_pHyprpicker->m_pLastSurface->m_pMonitor->scale)};
const auto CLICKPOS = Vector2D{g_pHyprpicker->m_vLastCoords.floor().x * SCALE.x, g_pHyprpicker->m_vLastCoords.floor().y * SCALE.y};
const auto COL = g_pHyprpicker->getColorFromPixel(g_pHyprpicker->m_pLastSurface, CLICKPOS);
switch (g_pHyprpicker->m_bSelectedOutputMode) {
case OUTPUT_CMYK: {
// http://www.codeproject.com/KB/applications/xcmyk.aspx
float r = 1 - COL.r / 255.0f, g = 1 - COL.g / 255.0f, b = 1 - COL.b / 255.0f;
float k = fmin3(r, g, b), K = 1 - k;
float c = (r - k) / K, m = (g - k) / K, y = (b - k) / K;
c = std::round(c * 100);
m = std::round(m * 100);
y = std::round(y * 100);
k = std::round(k * 100);
if (g_pHyprpicker->m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%im%g%% %g%% %g%% %g%%\033[0m", COL.r, COL.g, COL.b, c, m, y, k);
else
Debug::log(NONE, "%g%% %g%% %g%% %g%%", c, m, y, k);
if (g_pHyprpicker->m_bAutoCopy)
Clipboard::copy("%g%% %g%% %g%% %g%%", c, m, y, k);
g_pHyprpicker->finish();
break;
}
case OUTPUT_HEX: {
auto toHex = [](int i) -> std::string {
const char* DS = "0123456789ABCDEF";
std::string result = "";
result += DS[i / 16];
result += DS[i % 16];
return result;
};
if (g_pHyprpicker->m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%im#%s%s%s\033[0m", COL.r, COL.g, COL.b, toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str());
else
Debug::log(NONE, "#%s%s%s", toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str());
if (g_pHyprpicker->m_bAutoCopy)
Clipboard::copy("#%s%s%s", toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str());
g_pHyprpicker->finish();
break;
}
case OUTPUT_RGB: {
if (g_pHyprpicker->m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%im%i %i %i\033[0m", COL.r, COL.g, COL.b, COL.r, COL.g, COL.b);
else
Debug::log(NONE, "%i %i %i", COL.r, COL.g, COL.b);
if (g_pHyprpicker->m_bAutoCopy)
Clipboard::copy("%i %i %i", COL.r, COL.g, COL.b);
g_pHyprpicker->finish();
break;
}
case OUTPUT_HSL:
case OUTPUT_HSV: {
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
auto floatEq = [](float a, float b) -> bool {
return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
};
float h, s, l, v;
float r = COL.r / 255.0f, g = COL.g / 255.0f, b = COL.b / 255.0f;
float max = fmax3(r, g, b), min = fmin3(r, g, b);
float c = max - min;
v = max;
if (c == 0)
h = 0;
else if (v == r)
h = 60 * (0 + (g - b) / c);
else if (v == g)
h = 60 * (2 + (b - r) / c);
else /* v == b */
h = 60 * (4 + (r - g) / c);
float l_or_v;
if (g_pHyprpicker->m_bSelectedOutputMode == OUTPUT_HSL) {
l = (max + min) / 2;
s = (floatEq(l, 0.0f) || floatEq(l, 1.0f)) ? 0 : (v - l) / std::min(l, 1 - l);
l_or_v = std::round(l * 100);
} else {
v = max;
s = floatEq(v, 0.0f) ? 0 : c / v;
l_or_v = std::round(v * 100);
}
h = std::round(h);
s = std::round(s * 100);
if (g_pHyprpicker->m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%im%g %g%% %g%%\033[0m", COL.r, COL.g, COL.b, h, s, l_or_v);
else
Debug::log(NONE, "%g %g%% %g%%", h, s, l_or_v);
if (g_pHyprpicker->m_bAutoCopy)
Clipboard::copy("%g %g%% %g%%", h, s, l_or_v);
g_pHyprpicker->finish();
break;
}
}
g_pHyprpicker->finish();
}
void Events::handleKeyboardKeymap(void* data, wl_keyboard* wl_keyboard, uint format, int fd, uint size) {}
void Events::handleKeyboardKey(void* data, struct wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
if (key == 1) // escape
g_pHyprpicker->finish();
}
void Events::handleKeyboardEnter(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface, wl_array* keys) {}
void Events::handleKeyboardLeave(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface) {}
void Events::handleKeyboardModifiers(void* data, wl_keyboard* wl_keyboard, uint serial, uint mods_depressed, uint mods_latched, uint mods_locked, uint group) {}
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, stride);
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);
}

View file

@ -1,75 +0,0 @@
#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 handleKeyboardKeymap(void* data, wl_keyboard* wl_keyboard, uint format, int fd, uint size);
void handleKeyboardKey(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
void handleKeyboardEnter(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface, wl_array* keys);
void handleKeyboardLeave(void* data, wl_keyboard* wl_keyboard, uint serial, wl_surface* surface);
void handleKeyboardModifiers(void* data, wl_keyboard* wl_keyboard, uint serial, uint mods_depressed, uint mods_latched, uint mods_locked, uint group);
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_keyboard_listener keyboardListener = { .keymap = handleKeyboardKeymap, .enter = handleKeyboardEnter,.leave = handleKeyboardLeave, .key = handleKeyboardKey, .modifiers = handleKeyboardModifiers };
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 };
};

View file

@ -3,6 +3,6 @@
#include "../defines.hpp"
class CColor {
public:
public:
uint8_t r = 0, g = 0, b = 0, a = 0;
};

View file

@ -1,12 +1,9 @@
#include "LayerSurface.hpp"
#include "../events/Events.hpp"
#include "../hyprpicker.hpp"
CLayerSurface::CLayerSurface(SMonitor* pMonitor) {
m_pMonitor = pMonitor;
pSurface = wl_compositor_create_surface(g_pHyprpicker->m_pCompositor);
CLayerSurface::CLayerSurface(SMonitor* pMonitor) : m_pMonitor(pMonitor) {
pSurface = makeShared<CCWlSurface>(g_pHyprpicker->m_pCompositor->sendCreateSurface());
if (!pSurface) {
Debug::log(CRIT, "The compositor did not allow hyprpicker a surface!");
@ -14,7 +11,22 @@ CLayerSurface::CLayerSurface(SMonitor* pMonitor) {
return;
}
pLayerSurface = zwlr_layer_shell_v1_get_layer_surface(g_pHyprpicker->m_pLayerShell, pSurface, pMonitor->output, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "hyprpicker");
if (!g_pHyprpicker->m_bNoFractional) {
pViewport = makeShared<CCWpViewport>(g_pHyprpicker->m_pViewporter->sendGetViewport(pSurface->resource()));
// this will not actually be used, as we assume we'll be fullscreen and we can get the real dimensions from screencopy, but we'll have
// this for if we need it in the future
pFractionalScale = makeShared<CCWpFractionalScaleV1>(g_pHyprpicker->m_pFractionalMgr->sendGetFractionalScale(pSurface->resource()));
pFractionalScale->setPreferredScale([this](CCWpFractionalScaleV1* r, uint32_t scale120) { //
Debug::log(TRACE, "Received a preferredScale for %s: %.2f", m_pMonitor->name.c_str(), scale120 / 120.F);
fractionalScale = scale120 / 120.F;
wantsReload = true;
g_pHyprpicker->recheckACK();
});
}
pLayerSurface = makeShared<CCZwlrLayerSurfaceV1>(
g_pHyprpicker->m_pLayerShell->sendGetLayerSurface(pSurface->resource(), pMonitor->output->resource(), ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "hyprpicker"));
if (!pLayerSurface) {
Debug::log(CRIT, "The compositor did not allow hyprpicker a layersurface!");
@ -22,20 +34,63 @@ CLayerSurface::CLayerSurface(SMonitor* pMonitor) {
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);
pLayerSurface->setConfigure([this](CCZwlrLayerSurfaceV1* r, uint32_t serial, uint32_t width, uint32_t height) {
m_pMonitor->size = {(double)width, (double)height};
ACKSerial = serial;
wantsACK = true;
working = true;
g_pHyprpicker->recheckACK();
});
pLayerSurface->sendSetSize(0, 0);
pLayerSurface->sendSetAnchor((zwlrLayerSurfaceV1Anchor)(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT));
pLayerSurface->sendSetExclusiveZone(-1);
pLayerSurface->sendSetKeyboardInteractivity(1);
pSurface->sendCommit();
wl_display_flush(g_pHyprpicker->m_pWLDisplay);
}
CLayerSurface::~CLayerSurface() {
wl_surface_destroy(pSurface);
zwlr_layer_surface_v1_destroy(pLayerSurface);
pLayerSurface.reset();
pSurface.reset();
frameCallback.reset();
if (g_pHyprpicker->m_pWLDisplay)
wl_display_flush(g_pHyprpicker->m_pWLDisplay);
}
}
// this has to be a separate function because frameCallback.reset() will destroy the listener func
static void onCallbackDone(CLayerSurface* surf, uint32_t when) {
surf->frameCallback.reset();
g_pHyprpicker->renderSurface(surf);
}
void CLayerSurface::sendFrame() {
lastBuffer = lastBuffer == 0 ? 1 : 0;
const auto& PBUFFER = buffers[lastBuffer];
frameCallback = makeShared<CCWlCallback>(pSurface->sendFrame());
frameCallback->setDone([this](CCWlCallback* r, uint32_t when) { onCallbackDone(this, when); });
pSurface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
pSurface->sendAttach(PBUFFER->buffer.get(), 0, 0);
if (!g_pHyprpicker->m_bNoFractional) {
pSurface->sendSetBufferScale(1);
pViewport->sendSetDestination(m_pMonitor->size.x, m_pMonitor->size.y);
} else
pSurface->sendSetBufferScale(m_pMonitor->scale);
pSurface->sendCommit();
}
void CLayerSurface::markDirty() {
frameCallback = makeShared<CCWlCallback>(pSurface->sendFrame());
frameCallback->setDone([this](CCWlCallback* r, uint32_t when) { onCallbackDone(this, when); });
dirty = true;
}

View file

@ -6,31 +6,36 @@
struct SMonitor;
class CLayerSurface {
public:
public:
CLayerSurface(SMonitor*);
~CLayerSurface();
SMonitor* m_pMonitor = nullptr;
void sendFrame();
void markDirty();
zwlr_layer_surface_v1* pLayerSurface = nullptr;
wl_surface* pSurface = nullptr;
wl_surface* pCursorSurface = nullptr;
SMonitor* m_pMonitor = nullptr;
bool wantsACK = false;
uint32_t ACKSerial = 0;
bool working = false;
SP<CCZwlrLayerSurfaceV1> pLayerSurface = nullptr;
SP<CCWlSurface> pSurface = nullptr;
SP<CCWpViewport> pViewport = nullptr;
SP<CCWpFractionalScaleV1> pFractionalScale = nullptr;
int lastBuffer = 0;
SPoolBuffer buffers[2];
SPoolBuffer screenBuffer;
uint32_t scflags = 0;
float fractionalScale = 1.F;
bool wantsACK = false;
bool wantsReload = false;
uint32_t ACKSerial = 0;
bool working = false;
bool dirty = true;
int lastBuffer = 0;
SP<SPoolBuffer> buffers[2];
bool rendered = false;
SP<SPoolBuffer> screenBuffer;
uint32_t scflags = 0;
uint32_t screenBufferFormat = 0;
wl_callback* frame_callback = nullptr;
bool dirty = true;
wl_cursor_image* pCursorImg = nullptr;
bool rendered = false;
SP<CCWlCallback> frameCallback = nullptr;
};

121
src/helpers/Monitor.cpp Normal file
View file

@ -0,0 +1,121 @@
#include "Monitor.hpp"
#include "LayerSurface.hpp"
#include "../hyprpicker.hpp"
SMonitor::SMonitor(SP<CCWlOutput> output_) : output(output_) {
output->setGeometry([this](CCWlOutput* r, 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_) { //
transform = (wl_output_transform)transform_;
});
output->setDone([this](CCWlOutput* r) { //
ready = true;
});
output->setScale([this](CCWlOutput* r, int32_t scale_) { //
scale = scale_;
});
output->setName([this](CCWlOutput* r, const char* name_) { //
if (name_)
name = name_;
});
}
void SMonitor::initSCFrame() {
pSCFrame->setBuffer([this](CCZwlrScreencopyFrameV1* r, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) {
pLS->screenBufferFormat = format;
if (!pLS->screenBuffer)
pLS->screenBuffer = makeShared<SPoolBuffer>(Vector2D{(double)width, (double)height}, format, stride);
pSCFrame->sendCopy(pLS->screenBuffer->buffer->resource());
});
pSCFrame->setFlags([this](CCZwlrScreencopyFrameV1* r, uint32_t flags) {
pLS->scflags = flags;
g_pHyprpicker->recheckACK();
});
pSCFrame->setReady([this](CCZwlrScreencopyFrameV1* r, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
Vector2D transformedSize = pLS->screenBuffer->pixelSize;
if (pLS->m_pMonitor->transform % 2 == 1)
std::swap(transformedSize.x, transformedSize.y);
Debug::log(TRACE, "Frame ready: pixel %.0fx%.0f, xfmd: %.0fx%.0f", pLS->screenBuffer->pixelSize.x, pLS->screenBuffer->pixelSize.y, transformedSize.x, transformedSize.y);
SP<SPoolBuffer> newBuf = makeShared<SPoolBuffer>(transformedSize, pLS->screenBufferFormat, transformedSize.x * 4);
int bytesPerPixel = pLS->screenBuffer->stride / (int)pLS->screenBuffer->pixelSize.x;
void* data = pLS->screenBuffer->data;
if (bytesPerPixel == 4)
g_pHyprpicker->convertBuffer(pLS->screenBuffer);
else if (bytesPerPixel == 3) {
Debug::log(WARN, "24 bit formats are unsupported, hyprpicker may or may not work as intended!");
data = g_pHyprpicker->convert24To32Buffer(pLS->screenBuffer);
pLS->screenBuffer->paddedData = data;
} else {
Debug::log(CRIT, "Unsupported stride/bytes per pixel %i", bytesPerPixel);
g_pHyprpicker->finish(1);
}
cairo_surface_t* oldSurface = cairo_image_surface_create_for_data((unsigned char*)data, CAIRO_FORMAT_ARGB32, pLS->screenBuffer->pixelSize.x, pLS->screenBuffer->pixelSize.y,
pLS->screenBuffer->pixelSize.x * 4);
cairo_surface_flush(oldSurface);
newBuf->surface = cairo_image_surface_create_for_data((unsigned char*)newBuf->data, CAIRO_FORMAT_ARGB32, transformedSize.x, transformedSize.y, transformedSize.x * 4);
const auto PCAIRO = cairo_create(newBuf->surface);
auto cairoTransformMtx = [&](cairo_matrix_t* mtx) -> void {
const auto TR = pLS->m_pMonitor->transform % 4;
if (TR == 0)
return;
cairo_matrix_rotate(mtx, -M_PI_2 * (double)TR);
if (TR == 1)
cairo_matrix_translate(mtx, -transformedSize.x, 0);
else if (TR == 2)
cairo_matrix_translate(mtx, -transformedSize.x, -transformedSize.y);
else if (TR == 3)
cairo_matrix_translate(mtx, 0, -transformedSize.y);
// TODO: flipped
};
cairo_save(PCAIRO);
cairo_set_source_rgba(PCAIRO, 0, 0, 0, 0);
cairo_rectangle(PCAIRO, 0, 0, 0xFFFF, 0xFFFF);
cairo_fill(PCAIRO);
const auto PATTERNPRE = cairo_pattern_create_for_surface(oldSurface);
cairo_pattern_set_filter(PATTERNPRE, CAIRO_FILTER_BILINEAR);
cairo_matrix_t matrixPre;
cairo_matrix_init_identity(&matrixPre);
cairo_matrix_scale(&matrixPre, 1.0, 1.0);
cairoTransformMtx(&matrixPre);
cairo_pattern_set_matrix(PATTERNPRE, &matrixPre);
cairo_set_source(PCAIRO, PATTERNPRE);
cairo_paint(PCAIRO);
cairo_surface_flush(newBuf->surface);
cairo_pattern_destroy(PATTERNPRE);
cairo_destroy(PCAIRO);
cairo_surface_destroy(oldSurface);
pLS->screenBuffer = newBuf;
g_pHyprpicker->renderSurface(pLS);
pSCFrame.reset();
});
pSCFrame->setFailed([](CCZwlrScreencopyFrameV1* r) {
Debug::log(CRIT, "Failed to get a Screencopy!");
g_pHyprpicker->finish(1);
});
}

View file

@ -1,15 +1,24 @@
#pragma once
#include "../defines.hpp"
#include <hyprutils/math/Vector2D.hpp>
using namespace Hyprutils::Math;
class CLayerSurface;
struct SMonitor {
std::string name = "";
wl_output* output = nullptr;
uint32_t wayland_name = 0;
Vector2D size;
int scale;
SMonitor(SP<CCWlOutput> output_);
void initSCFrame();
bool ready = false;
std::string name = "";
SP<CCWlOutput> output = nullptr;
uint32_t wayland_name = 0;
Vector2D size;
int scale;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
zwlr_screencopy_frame_v1* pSCFrame = nullptr;
bool ready = false;
CLayerSurface* pLS = nullptr;
SP<CCZwlrScreencopyFrameV1> pSCFrame = nullptr;
};

View file

@ -0,0 +1,42 @@
#include "PoolBuffer.hpp"
#include "../hyprpicker.hpp"
SPoolBuffer::SPoolBuffer(const Vector2D& pixelSize_, uint32_t format_, uint32_t stride_) : stride(stride_), pixelSize(pixelSize_), format(format_) {
const size_t SIZE = stride * pixelSize.y;
const auto FD = g_pHyprpicker->createPoolFile(SIZE, name);
if (FD == -1) {
Debug::log(CRIT, "Unable to create pool file!");
g_pHyprpicker->finish(1);
}
const auto DATA = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
size = SIZE;
data = DATA;
auto POOL = makeShared<CCWlShmPool>(g_pHyprpicker->m_pSHM->sendCreatePool(FD, SIZE));
buffer = makeShared<CCWlBuffer>(POOL->sendCreateBuffer(0, pixelSize.x, pixelSize.y, stride, format));
buffer->setRelease([this](CCWlBuffer* r) { busy = false; });
POOL.reset();
close(FD);
}
SPoolBuffer::~SPoolBuffer() {
buffer.reset();
cairo_destroy(cairo);
cairo_surface_destroy(surface);
munmap(data, size);
cairo = nullptr;
surface = nullptr;
unlink(name.c_str());
if (paddedData)
free(paddedData);
}

View file

@ -3,21 +3,24 @@
#include "../defines.hpp"
struct SPoolBuffer {
wl_buffer* buffer = nullptr;
SPoolBuffer(const Vector2D& size, uint32_t format, uint32_t stride);
~SPoolBuffer();
SP<CCWlBuffer> buffer = nullptr;
cairo_surface_t* surface = nullptr;
cairo_t* cairo = nullptr;
void* data = nullptr;
cairo_t* cairo = nullptr;
void* data = nullptr;
// malloc'ed buffer for 24bit formats
void* paddedData = nullptr;
void* paddedData = nullptr;
size_t size = 0;
uint32_t stride = 0;
Vector2D pixelSize;
size_t size = 0;
uint32_t stride = 0;
Vector2D pixelSize;
uint32_t format;
uint32_t format;
std::string name;
bool busy = false;
bool busy = false;
};

View file

@ -1,23 +0,0 @@
#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);
}

View file

@ -1,39 +0,0 @@
#pragma once
#include <math.h>
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();
};

View file

@ -1,8 +1,16 @@
#include "hyprpicker.hpp"
#include <csignal>
#include "events/Events.hpp"
void sigHandler(int sig) {
g_pHyprpicker->m_vLayerSurfaces.clear();
exit(0);
}
void CHyprpicker::init() {
m_pXKBContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!m_pXKBContext)
Debug::log(ERR, "Failed to create xkb context");
m_pWLDisplay = wl_display_connect(nullptr);
if (!m_pWLDisplay) {
@ -11,22 +19,92 @@ void CHyprpicker::init() {
return;
}
m_pWLRegistry = wl_display_get_registry(m_pWLDisplay);
signal(SIGTERM, sigHandler);
wl_registry_add_listener(m_pWLRegistry, &Events::registryListener, nullptr);
m_pRegistry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(m_pWLDisplay));
m_pRegistry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) {
if (strcmp(interface, wl_compositor_interface.name) == 0) {
m_pCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &wl_compositor_interface, 4));
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
m_pSHM = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &wl_shm_interface, 1));
} else if (strcmp(interface, wl_output_interface.name) == 0) {
m_mtTickMutex.lock();
const auto PMONITOR = g_pHyprpicker->m_vMonitors
.emplace_back(std::make_unique<SMonitor>(
makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &wl_output_interface, 4))))
.get();
PMONITOR->wayland_name = name;
m_mtTickMutex.unlock();
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
m_pLayerShell = makeShared<CCZwlrLayerShellV1>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &zwlr_layer_shell_v1_interface, 1));
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
m_pSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &wl_seat_interface, 1));
m_pSeat->setCapabilities([this](CCWlSeat* seat, uint32_t caps) {
if (caps & WL_SEAT_CAPABILITY_POINTER) {
if (!m_pPointer) {
m_pPointer = makeShared<CCWlPointer>(m_pSeat->sendGetPointer());
initMouse();
if (m_pCursorShapeMgr)
m_pCursorShapeDevice = makeShared<CCWpCursorShapeDeviceV1>(m_pCursorShapeMgr->sendGetPointer(m_pPointer->resource()));
}
} else {
Debug::log(CRIT, "Hyprpicker cannot work without a pointer!");
g_pHyprpicker->finish(1);
}
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
if (!m_pKeyboard) {
m_pKeyboard = makeShared<CCWlKeyboard>(m_pSeat->sendGetKeyboard());
initKeyboard();
}
} else
m_pKeyboard.reset();
});
} else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) {
m_pScreencopyMgr =
makeShared<CCZwlrScreencopyManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &zwlr_screencopy_manager_v1_interface, 1));
} else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
m_pCursorShapeMgr =
makeShared<CCWpCursorShapeManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &wp_cursor_shape_manager_v1_interface, 1));
} else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {
m_pFractionalMgr =
makeShared<CCWpFractionalScaleManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &wp_fractional_scale_manager_v1_interface, 1));
} else if (strcmp(interface, wp_viewporter_interface.name) == 0) {
m_pViewporter = makeShared<CCWpViewporter>((wl_proxy*)wl_registry_bind((wl_registry*)m_pRegistry->resource(), name, &wp_viewporter_interface, 1));
}
});
wl_display_roundtrip(m_pWLDisplay);
if (!m_pCursorShapeMgr)
Debug::log(ERR, "cursor_shape_v1 not supported, cursor won't be affected");
if (!m_pScreencopyMgr) {
Debug::log(CRIT, "zwlr_screencopy_v1 not supported, can't proceed");
exit(1);
}
if (!m_pFractionalMgr) {
Debug::log(WARN, "wp_fractional_scale_v1 not supported, fractional scaling won't work");
m_bNoFractional = true;
}
if (!m_pViewporter) {
Debug::log(WARN, "wp_viewporter not supported, fractional scaling won't work");
m_bNoFractional = true;
}
for (auto& m : m_vMonitors) {
m_vLayerSurfaces.emplace_back(std::make_unique<CLayerSurface>(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);
m->pSCFrame = makeShared<CCZwlrScreencopyFrameV1>(m_pScreencopyMgr->sendCaptureOutput(false, m->output->resource()));
m->pLS = m_vLayerSurfaces.back().get();
m->initSCFrame();
}
wl_display_roundtrip(m_pWLDisplay);
@ -42,15 +120,24 @@ void CHyprpicker::init() {
}
void CHyprpicker::finish(int code) {
for (auto& ls : m_vLayerSurfaces) {
destroyBuffer(&ls->buffers[0]);
destroyBuffer(&ls->buffers[1]);
destroyBuffer(&ls->screenBuffer);
}
m_vLayerSurfaces.clear();
if (m_pWLDisplay) {
m_vLayerSurfaces.clear();
m_vMonitors.clear();
m_pCompositor.reset();
m_pRegistry.reset();
m_pSHM.reset();
m_pLayerShell.reset();
m_pScreencopyMgr.reset();
m_pCursorShapeMgr.reset();
m_pCursorShapeDevice.reset();
m_pSeat.reset();
m_pKeyboard.reset();
m_pPointer.reset();
m_pViewporter.reset();
m_pFractionalMgr.reset();
wl_display_disconnect(m_pWLDisplay);
m_pWLDisplay = nullptr;
}
@ -60,22 +147,19 @@ void CHyprpicker::finish(int 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->wantsACK || ls->wantsReload) && ls->screenBuffer) {
if (ls->wantsACK)
ls->pLayerSurface->sendAckConfigure(ls->ACKSerial);
ls->wantsACK = false;
ls->wantsReload = false;
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,
ls->m_pMonitor->size.x * ls->m_pMonitor->scale * 4);
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->m_pMonitor->size.x * ls->m_pMonitor->scale * 4);
const auto MONITORSIZE =
(ls->screenBuffer && !g_pHyprpicker->m_bNoFractional ? ls->m_pMonitor->size * ls->fractionalScale : ls->m_pMonitor->size * ls->m_pMonitor->scale).round();
int XCURSOR_SIZE = 24;
if (getenv("XCURSOR_SIZE")) {
XCURSOR_SIZE = std::stoi(getenv("XCURSOR_SIZE"));
}
ls->pCursorImg = wl_cursor_theme_get_cursor(wl_cursor_theme_load(getenv("XCURSOR_THEME"), XCURSOR_SIZE * ls->m_pMonitor->scale, m_pWLSHM), "left_ptr")->images[0];
if (!ls->buffers[0] || ls->buffers[0]->pixelSize != MONITORSIZE) {
Debug::log(TRACE, "making new buffers: size changed to %.0fx%.0f", MONITORSIZE.x, MONITORSIZE.y);
ls->buffers[0] = makeShared<SPoolBuffer>(MONITORSIZE, WL_SHM_FORMAT_ARGB8888, MONITORSIZE.x * 4);
ls->buffers[1] = makeShared<SPoolBuffer>(MONITORSIZE, WL_SHM_FORMAT_ARGB8888, MONITORSIZE.x * 4);
}
}
}
@ -85,32 +169,23 @@ void CHyprpicker::recheckACK() {
void CHyprpicker::markDirty() {
for (auto& ls : m_vLayerSurfaces) {
if (ls->frame_callback)
if (ls->frameCallback)
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;
ls->markDirty();
}
}
SPoolBuffer* CHyprpicker::getBufferForLS(CLayerSurface* pLS) {
SPoolBuffer* returns = nullptr;
SP<SPoolBuffer> CHyprpicker::getBufferForLS(CLayerSurface* pLS) {
SP<SPoolBuffer> returns = nullptr;
for (auto i = 0; i < 2; ++i) {
if (pLS->buffers[i].busy)
if (!pLS->buffers[i] || pLS->buffers[i]->busy)
continue;
returns = &pLS->buffers[i];
returns = pLS->buffers[i];
}
if (!returns)
return nullptr;
returns->busy = true;
return returns;
}
@ -157,57 +232,7 @@ int CHyprpicker::createPoolFile(size_t size, std::string& name) {
return FD;
}
void CHyprpicker::createBuffer(SPoolBuffer* pBuffer, int32_t w, int32_t h, uint32_t format, uint32_t stride) {
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;
pBuffer->stride = stride;
}
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());
if (pBuffer->paddedData) {
free(pBuffer->paddedData);
}
}
void CHyprpicker::createSeat(wl_seat* pSeat) {
wl_seat_add_listener(pSeat, &Events::seatListener, pSeat);
}
void CHyprpicker::convertBuffer(SPoolBuffer* pBuffer) {
void CHyprpicker::convertBuffer(SP<SPoolBuffer> pBuffer) {
switch (pBuffer->format) {
case WL_SHM_FORMAT_ARGB8888:
case WL_SHM_FORMAT_XRGB8888: break;
@ -223,12 +248,33 @@ void CHyprpicker::convertBuffer(SPoolBuffer* pBuffer) {
unsigned char green;
unsigned char red;
unsigned char alpha;
}* px = (struct pixel*)(data + y * (int)pBuffer->pixelSize.x * 4 + x * 4);
}* px = (struct pixel*)(data + (y * (int)pBuffer->pixelSize.x * 4) + (x * 4));
std::swap(px->red, px->blue);
}
}
} break;
case WL_SHM_FORMAT_XRGB2101010:
case WL_SHM_FORMAT_XBGR2101010: {
uint8_t* data = (uint8_t*)pBuffer->data;
const bool FLIP = pBuffer->format == WL_SHM_FORMAT_XBGR2101010;
for (int y = 0; y < pBuffer->pixelSize.y; ++y) {
for (int x = 0; x < pBuffer->pixelSize.x; ++x) {
uint32_t* px = (uint32_t*)(data + (y * (int)pBuffer->pixelSize.x * 4) + (x * 4));
// conv to 8 bit
uint8_t R = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000000000000001111111111) >> 0) / 1023.0));
uint8_t G = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000011111111110000000000) >> 10) / 1023.0));
uint8_t B = (uint8_t)std::round((255.0 * (((*px) & 0b00111111111100000000000000000000) >> 20) / 1023.0));
uint8_t A = (uint8_t)std::round((255.0 * (((*px) & 0b11000000000000000000000000000000) >> 30) / 3.0));
// write 8-bit values
*px = ((FLIP ? B : R) << 0) + (G << 8) + ((FLIP ? R : B) << 16) + (A << 24);
}
}
} break;
default: {
Debug::log(CRIT, "Unsupported format %i", pBuffer->format);
}
@ -237,7 +283,7 @@ void CHyprpicker::convertBuffer(SPoolBuffer* pBuffer) {
}
// Mallocs a new buffer, which needs to be free'd!
void* convert24To32Buffer(SPoolBuffer* pBuffer) {
void* CHyprpicker::convert24To32Buffer(SP<SPoolBuffer> pBuffer) {
uint8_t* newBuffer = (uint8_t*)malloc((size_t)pBuffer->pixelSize.x * pBuffer->pixelSize.y * 4);
int newBufferStride = pBuffer->pixelSize.x * 4;
uint8_t* oldBuffer = (uint8_t*)pBuffer->data;
@ -251,15 +297,15 @@ void* convert24To32Buffer(SPoolBuffer* pBuffer) {
unsigned char blue;
unsigned char green;
unsigned char red;
}* srcPx = (struct pixel3*)(oldBuffer + y * pBuffer->stride + x * 3);
}* srcPx = (struct pixel3*)(oldBuffer + (y * pBuffer->stride) + (x * 3));
struct pixel4 {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* dstPx = (struct pixel4*)(newBuffer + y * newBufferStride + x * 4);
*dstPx = {srcPx->red, srcPx->green, srcPx->blue, 0xFF};
}* dstPx = (struct pixel4*)(newBuffer + (y * newBufferStride) + (x * 4));
*dstPx = {.blue = srcPx->red, .green = srcPx->green, .red = srcPx->blue, .alpha = 0xFF};
}
}
} break;
@ -271,15 +317,15 @@ void* convert24To32Buffer(SPoolBuffer* pBuffer) {
unsigned char red;
unsigned char green;
unsigned char blue;
}* srcPx = (struct pixel3*)(oldBuffer + y * pBuffer->stride + x * 3);
}* srcPx = (struct pixel3*)(oldBuffer + (y * pBuffer->stride) + (x * 3));
struct pixel4 {
// big-endian ARGB
unsigned char alpha;
unsigned char red;
unsigned char green;
unsigned char blue;
}* dstPx = (struct pixel4*)(newBuffer + y * newBufferStride + x * 4);
*dstPx = {0xFF, srcPx->red, srcPx->green, srcPx->blue};
}* dstPx = (struct pixel4*)(newBuffer + (y * newBufferStride) + (x * 4));
*dstPx = {.alpha = 0xFF, .red = srcPx->red, .green = srcPx->green, .blue = srcPx->blue};
}
}
} break;
@ -294,46 +340,33 @@ void* convert24To32Buffer(SPoolBuffer* pBuffer) {
void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
const auto PBUFFER = getBufferForLS(pSurface);
if (!PBUFFER || !pSurface->screenBuffer.buffer)
if (!PBUFFER || !pSurface->screenBuffer) {
Debug::log(ERR, PBUFFER ? "renderSurface: pSurface->screenBuffer null" : "renderSurface: PBUFFER null");
return;
if (!pSurface->screenBuffer.surface) {
int bytesPerPixel = pSurface->screenBuffer.stride / (int)pSurface->screenBuffer.pixelSize.x;
void* data = pSurface->screenBuffer.data;
if (bytesPerPixel == 4) {
convertBuffer(&pSurface->screenBuffer);
} else if (bytesPerPixel == 3) {
Debug::log(WARN, "24 bit formats are unsupported, hyprpicker may or may not work as intended!");
data = convert24To32Buffer(&pSurface->screenBuffer);
pSurface->screenBuffer.paddedData = data;
} else {
Debug::log(CRIT, "Unsupported stride/bytes per pixel %i", bytesPerPixel);
g_pHyprpicker->finish(1);
}
pSurface->screenBuffer.surface = cairo_image_surface_create_for_data((unsigned char*)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, pSurface->m_pMonitor->size.x * pSurface->m_pMonitor->scale,
pSurface->m_pMonitor->size.y * pSurface->m_pMonitor->scale, PBUFFER->pixelSize.x * 4);
PBUFFER->cairo = cairo_create(PBUFFER->surface);
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_rectangle(PCAIRO, 0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
cairo_fill(PCAIRO);
if (pSurface == g_pHyprpicker->m_pLastSurface && !forceInactive) {
const auto SCALEBUFS = Vector2D{pSurface->screenBuffer.pixelSize.x / PBUFFER->pixelSize.x, pSurface->screenBuffer.pixelSize.y / PBUFFER->pixelSize.y};
const auto SCALECURSOR = Vector2D{
g_pHyprpicker->m_pLastSurface->screenBuffer.pixelSize.x / (g_pHyprpicker->m_pLastSurface->buffers[0].pixelSize.x / g_pHyprpicker->m_pLastSurface->m_pMonitor->scale),
g_pHyprpicker->m_pLastSurface->screenBuffer.pixelSize.y / (g_pHyprpicker->m_pLastSurface->buffers[0].pixelSize.y / g_pHyprpicker->m_pLastSurface->m_pMonitor->scale)};
const auto CLICKPOS = Vector2D{g_pHyprpicker->m_vLastCoords.floor().x * SCALECURSOR.x, g_pHyprpicker->m_vLastCoords.floor().y * SCALECURSOR.y};
if (pSurface == m_pLastSurface && !forceInactive) {
const auto SCALEBUFS = pSurface->screenBuffer->pixelSize / PBUFFER->pixelSize;
const auto MOUSECOORDSABS = m_vLastCoords.floor() / pSurface->m_pMonitor->size;
const auto CLICKPOS = MOUSECOORDSABS * PBUFFER->pixelSize;
const auto PATTERNPRE = cairo_pattern_create_for_surface(pSurface->screenBuffer.surface);
Debug::log(TRACE, "renderSurface: scalebufs %.2fx%.2f", SCALEBUFS.x, SCALEBUFS.y);
const auto PATTERNPRE = cairo_pattern_create_for_surface(pSurface->screenBuffer->surface);
cairo_pattern_set_filter(PATTERNPRE, CAIRO_FILTER_BILINEAR);
cairo_matrix_t matrixPre;
cairo_matrix_init_identity(&matrixPre);
@ -355,17 +388,20 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
// | |
// | --------- |
//
// (hex code here)
cairo_restore(PCAIRO);
if (!g_pHyprpicker->m_bNoZoom) {
if (!m_bNoZoom) {
cairo_save(PCAIRO);
const auto PIXCOLOR = getColorFromPixel(pSurface, CLICKPOS);
const auto CLICKPOSBUF = CLICKPOS / PBUFFER->pixelSize * pSurface->screenBuffer->pixelSize;
const auto PIXCOLOR = getColorFromPixel(pSurface, CLICKPOSBUF);
cairo_set_source_rgba(PCAIRO, PIXCOLOR.r / 255.f, PIXCOLOR.g / 255.f, PIXCOLOR.b / 255.f, PIXCOLOR.a / 255.f);
cairo_scale(PCAIRO, 1, 1);
cairo_arc(PCAIRO, m_vLastCoords.x * pSurface->m_pMonitor->scale, m_vLastCoords.y * pSurface->m_pMonitor->scale, 105 / SCALEBUFS.x, 0, 2 * M_PI);
cairo_arc(PCAIRO, CLICKPOS.x, CLICKPOS.y, 105 / SCALEBUFS.x, 0, 2 * M_PI);
cairo_clip(PCAIRO);
cairo_fill(PCAIRO);
@ -376,33 +412,85 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
cairo_restore(PCAIRO);
cairo_save(PCAIRO);
const auto PATTERN = cairo_pattern_create_for_surface(pSurface->screenBuffer.surface);
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, CLICKPOS.x + 0.5f, CLICKPOS.y + 0.5f);
cairo_matrix_translate(&matrix, CLICKPOSBUF.x + 0.5f, CLICKPOSBUF.y + 0.5f);
cairo_matrix_scale(&matrix, 0.1f, 0.1f);
cairo_matrix_translate(&matrix, -CLICKPOS.x / SCALEBUFS.x - 0.5f, -CLICKPOS.y / SCALEBUFS.y - 0.5f);
cairo_matrix_translate(&matrix, (-CLICKPOSBUF.x / SCALEBUFS.x) - 0.5f, (-CLICKPOSBUF.y / SCALEBUFS.y) - 0.5f);
cairo_pattern_set_matrix(PATTERN, &matrix);
cairo_set_source(PCAIRO, PATTERN);
cairo_arc(PCAIRO, m_vLastCoords.x * pSurface->m_pMonitor->scale, m_vLastCoords.y * pSurface->m_pMonitor->scale, 100 / SCALEBUFS.x, 0, 2 * M_PI);
cairo_arc(PCAIRO, CLICKPOS.x, CLICKPOS.y, 100 / SCALEBUFS.x, 0, 2 * M_PI);
cairo_clip(PCAIRO);
cairo_paint(PCAIRO);
cairo_surface_flush(PBUFFER->surface);
if (!m_bDisableHexPreview) {
const auto currentColor = getColorFromPixel(pSurface, CLICKPOS);
std::string hexBuffer;
if (m_bUseLowerCase)
hexBuffer = std::format("#{:02x}{:02x}{:02x}", currentColor.r, currentColor.g, currentColor.b);
else
hexBuffer = std::format("#{:02X}{:02X}{:02X}", currentColor.r, currentColor.g, currentColor.b);
cairo_set_source_rgba(PCAIRO, 0.0, 0.0, 0.0, 0.5);
double x, y, width = 85, height = 28, radius = 6;
if (CLICKPOS.y > (PBUFFER->pixelSize.y - 50) && CLICKPOS.x > (PBUFFER->pixelSize.x - 100)) {
x = CLICKPOS.x - 80;
y = CLICKPOS.y - 40;
} else if (CLICKPOS.y > (PBUFFER->pixelSize.y - 50)) {
x = CLICKPOS.x;
y = CLICKPOS.y - 40;
} else if (CLICKPOS.x > (PBUFFER->pixelSize.x - 100)) {
x = CLICKPOS.x - 80;
y = CLICKPOS.y + 20;
} else {
x = CLICKPOS.x;
y = CLICKPOS.y + 20;
}
cairo_move_to(PCAIRO, x + radius, y);
cairo_arc(PCAIRO, x + width - radius, y + radius, radius, -M_PI_2, 0);
cairo_arc(PCAIRO, x + width - radius, y + height - radius, radius, 0, M_PI_2);
cairo_arc(PCAIRO, x + radius, y + height - radius, radius, M_PI_2, M_PI);
cairo_arc(PCAIRO, x + radius, y + radius, radius, M_PI, -M_PI_2);
cairo_close_path(PCAIRO);
cairo_fill(PCAIRO);
cairo_set_source_rgba(PCAIRO, 1.0, 1.0, 1.0, 1.0);
cairo_select_font_face(PCAIRO, "monospace", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(PCAIRO, 18);
double padding = 5.0;
double textX = x + padding;
if (CLICKPOS.y > (PBUFFER->pixelSize.y - 50) && CLICKPOS.x > (PBUFFER->pixelSize.x - 100))
cairo_move_to(PCAIRO, textX, CLICKPOS.y - 20);
else if (CLICKPOS.y > (PBUFFER->pixelSize.y - 50))
cairo_move_to(PCAIRO, textX, CLICKPOS.y - 20);
else if (CLICKPOS.x > (PBUFFER->pixelSize.x - 100))
cairo_move_to(PCAIRO, textX, CLICKPOS.y + 40);
else
cairo_move_to(PCAIRO, textX, CLICKPOS.y + 40);
cairo_show_text(PCAIRO, hexBuffer.c_str());
cairo_surface_flush(PBUFFER->surface);
}
cairo_restore(PCAIRO);
cairo_pattern_destroy(PATTERN);
}
} else if (!g_pHyprpicker->m_bRenderInactive) {
} else if (!m_bRenderInactive) {
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_SOURCE);
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_rectangle(PCAIRO, 0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
cairo_fill(PCAIRO);
} else {
const auto SCALEBUFS = Vector2D{pSurface->screenBuffer.pixelSize.x / PBUFFER->pixelSize.x, pSurface->screenBuffer.pixelSize.y / PBUFFER->pixelSize.y};
const auto PATTERNPRE = cairo_pattern_create_for_surface(pSurface->screenBuffer.surface);
const auto SCALEBUFS = pSurface->screenBuffer->pixelSize / PBUFFER->pixelSize;
const auto PATTERNPRE = cairo_pattern_create_for_surface(pSurface->screenBuffer->surface);
cairo_pattern_set_filter(PATTERNPRE, CAIRO_FILTER_BILINEAR);
cairo_matrix_t matrixPre;
cairo_matrix_init_identity(&matrixPre);
@ -412,40 +500,247 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
cairo_paint(PCAIRO);
cairo_surface_flush(PBUFFER->surface);
cairo_pattern_destroy(PATTERNPRE);
}
sendFrame(pSurface);
pSurface->sendFrame();
cairo_destroy(PCAIRO);
cairo_surface_destroy(PBUFFER->surface);
PBUFFER->busy = true;
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_set_buffer_scale(pSurface->pSurface, pSurface->m_pMonitor->scale);
wl_surface_damage_buffer(pSurface->pSurface, 0, 0, INT32_MAX, INT32_MAX);
wl_surface_commit(pSurface->pSurface);
pSurface->dirty = false;
}
CColor CHyprpicker::getColorFromPixel(CLayerSurface* pLS, Vector2D pix) {
void* dataSrc = pLS->screenBuffer.paddedData ? pLS->screenBuffer.paddedData : pLS->screenBuffer.data;
pix = pix.floor();
if (pix.x >= pLS->screenBuffer->pixelSize.x || pix.y >= pLS->screenBuffer->pixelSize.y || pix.x < 0 || pix.y < 0)
return CColor{.r = 0, .g = 0, .b = 0, .a = 0};
void* dataSrc = pLS->screenBuffer->paddedData ? pLS->screenBuffer->paddedData : pLS->screenBuffer->data;
struct pixel {
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* px = (struct pixel*)((char*)dataSrc + (int)pix.y * (int)pLS->screenBuffer.pixelSize.x * 4 + (int)pix.x * 4);
}* px = (struct pixel*)((char*)dataSrc + ((ptrdiff_t)pix.y * (int)pLS->screenBuffer->pixelSize.x * 4) + ((ptrdiff_t)pix.x * 4));
return CColor{(uint8_t)px->red, (uint8_t)px->green, (uint8_t)px->blue, (uint8_t)px->alpha};
return CColor{.r = px->red, .g = px->green, .b = px->blue, .a = px->alpha};
}
void CHyprpicker::initKeyboard() {
m_pKeyboard->setKeymap([this](CCWlKeyboard* r, wl_keyboard_keymap_format format, int32_t fd, uint32_t size) {
if (!m_pXKBContext)
return;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
Debug::log(ERR, "Could not recognise keymap format");
return;
}
const char* buf = (const char*)mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED) {
Debug::log(ERR, "Failed to mmap xkb keymap: %d", errno);
return;
}
m_pXKBKeymap = xkb_keymap_new_from_buffer(m_pXKBContext, buf, size - 1, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap((void*)buf, size);
close(fd);
if (!m_pXKBKeymap) {
Debug::log(ERR, "Failed to compile xkb keymap");
return;
}
m_pXKBState = xkb_state_new(m_pXKBKeymap);
if (!m_pXKBState) {
Debug::log(ERR, "Failed to create xkb state");
return;
}
});
m_pKeyboard->setKey([this](CCWlKeyboard* r, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
return;
if (m_pXKBState) {
if (xkb_state_key_get_one_sym(m_pXKBState, key + 8) == XKB_KEY_Escape)
finish();
} else if (key == 1) // Assume keycode 1 is escape
finish();
});
}
void CHyprpicker::initMouse() {
m_pPointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surface, 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);
m_vLastCoords = {x, y};
for (auto& ls : m_vLayerSurfaces) {
if (ls->pSurface->resource() == surface) {
m_pLastSurface = ls.get();
break;
}
}
m_pCursorShapeDevice->sendSetShape(serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR);
markDirty();
});
m_pPointer->setLeave([this](CCWlPointer* r, uint32_t timeMs, wl_proxy* surface) {
for (auto& ls : m_vLayerSurfaces) {
if (ls->pSurface->resource() == surface) {
if (m_pLastSurface == ls.get())
m_pLastSurface = nullptr;
break;
}
}
markDirty();
});
m_pPointer->setMotion([this](CCWlPointer* r, uint32_t timeMs, 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);
m_vLastCoords = {x, y};
markDirty();
});
m_pPointer->setButton([this](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) {
auto fmax3 = [](float a, float b, float c) -> float { return (a > b && a > c) ? a : (b > c) ? b : c; };
auto fmin3 = [](float a, float b, float c) -> float { return (a < b && a < c) ? a : (b < c) ? b : c; };
// relative brightness of a color
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
const auto FLUMI = [](const float& c) -> float { return c <= 0.03928 ? c / 12.92 : powf((c + 0.055) / 1.055, 2.4); };
// get the px and print it
const auto MOUSECOORDSABS = m_vLastCoords.floor() / m_pLastSurface->m_pMonitor->size;
const auto CLICKPOS = MOUSECOORDSABS * m_pLastSurface->screenBuffer->pixelSize;
const auto COL = getColorFromPixel(m_pLastSurface, CLICKPOS);
// threshold: (lumi_white + 0.05) / (x + 0.05) == (x + 0.05) / (lumi_black + 0.05)
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
const uint8_t FG = 0.2126 * FLUMI(COL.r / 255.0f) + 0.7152 * FLUMI(COL.g / 255.0f) + 0.0722 * FLUMI(COL.b / 255.0f) > 0.17913 ? 0 : 255;
switch (m_bSelectedOutputMode) {
case OUTPUT_CMYK: {
// http://www.codeproject.com/KB/applications/xcmyk.aspx
float r = 1 - (COL.r / 255.0f), g = 1 - (COL.g / 255.0f), b = 1 - (COL.b / 255.0f);
float k = fmin3(r, g, b), K = (k == 1) ? 1 : 1 - k;
float c = (r - k) / K, m = (g - k) / K, y = (b - k) / K;
c = std::round(c * 100);
m = std::round(m * 100);
y = std::round(y * 100);
k = std::round(k * 100);
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im%g%% %g%% %g%% %g%%\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, c, m, y, k);
else
Debug::log(NONE, "%g%% %g%% %g%% %g%%", c, m, y, k);
if (m_bAutoCopy)
Clipboard::copy("%g%% %g%% %g%% %g%%", c, m, y, k);
finish();
break;
}
case OUTPUT_HEX: {
auto toHex = [this](int i) -> std::string {
const char* DS = m_bUseLowerCase ? "0123456789abcdef" : "0123456789ABCDEF";
std::string result = "";
result += DS[i / 16];
result += DS[i % 16];
return result;
};
auto hexR = toHex(COL.r);
auto hexG = toHex(COL.g);
auto hexB = toHex(COL.b);
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im#%s%s%s\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, toHex(COL.r).c_str(), toHex(COL.g).c_str(),
toHex(COL.b).c_str());
else
Debug::log(NONE, "#%s%s%s", toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str());
if (m_bAutoCopy)
Clipboard::copy("#%s%s%s", toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str());
finish();
break;
}
case OUTPUT_RGB: {
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im%i %i %i\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, COL.r, COL.g, COL.b);
else
Debug::log(NONE, "%i %i %i", COL.r, COL.g, COL.b);
if (m_bAutoCopy)
Clipboard::copy("%i %i %i", COL.r, COL.g, COL.b);
finish();
break;
}
case OUTPUT_HSL:
case OUTPUT_HSV: {
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
auto floatEq = [](float a, float b) -> bool {
return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
};
float h, s, l, v;
float r = COL.r / 255.0f, g = COL.g / 255.0f, b = COL.b / 255.0f;
float max = fmax3(r, g, b), min = fmin3(r, g, b);
float c = max - min;
v = max;
if (c == 0)
h = 0;
else if (v == r)
h = 60 * (0 + (g - b) / c);
else if (v == g)
h = 60 * (2 + (b - r) / c);
else /* v == b */
h = 60 * (4 + (r - g) / c);
float l_or_v;
if (m_bSelectedOutputMode == OUTPUT_HSL) {
l = (max + min) / 2;
s = (floatEq(l, 0.0f) || floatEq(l, 1.0f)) ? 0 : (v - l) / std::min(l, 1 - l);
l_or_v = std::round(l * 100);
} else {
v = max;
s = floatEq(v, 0.0f) ? 0 : c / v;
l_or_v = std::round(v * 100);
}
h = std::round(h < 0 ? h + 360 : h);
s = std::round(s * 100);
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im%g %g%% %g%%\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, h, s, l_or_v);
else
Debug::log(NONE, "%g %g%% %g%%", h, s, l_or_v);
if (m_bAutoCopy)
Clipboard::copy("%g %g%% %g%%", h, s, l_or_v);
finish();
break;
}
}
finish();
});
}

View file

@ -4,8 +4,7 @@
#include "helpers/LayerSurface.hpp"
#include "helpers/PoolBuffer.hpp"
enum eOutputMode
{
enum eOutputMode {
OUTPUT_CMYK = 0,
OUTPUT_HEX,
OUTPUT_RGB,
@ -19,45 +18,56 @@ class CHyprpicker {
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;
SP<CCWlCompositor> m_pCompositor;
SP<CCWlRegistry> m_pRegistry;
SP<CCWlShm> m_pSHM;
SP<CCZwlrLayerShellV1> m_pLayerShell;
SP<CCZwlrScreencopyManagerV1> m_pScreencopyMgr;
SP<CCWpCursorShapeManagerV1> m_pCursorShapeMgr;
SP<CCWpCursorShapeDeviceV1> m_pCursorShapeDevice;
SP<CCWlSeat> m_pSeat;
SP<CCWlKeyboard> m_pKeyboard;
SP<CCWlPointer> m_pPointer;
SP<CCWpFractionalScaleManagerV1> m_pFractionalMgr;
SP<CCWpViewporter> m_pViewporter;
wl_display* m_pWLDisplay = nullptr;
xkb_context* m_pXKBContext = nullptr;
xkb_keymap* m_pXKBKeymap = nullptr;
xkb_state* m_pXKBState = nullptr;
eOutputMode m_bSelectedOutputMode = OUTPUT_HEX;
bool m_bFancyOutput = true;
bool m_bAutoCopy = false;
bool m_bRenderInactive = false;
bool m_bNoZoom = false;
bool m_bAutoCopy = false;
bool m_bRenderInactive = false;
bool m_bNoZoom = false;
bool m_bNoFractional = false;
bool m_bDisableHexPreview = false;
bool m_bUseLowerCase = false;
bool m_bRunning = true;
std::vector<std::unique_ptr<SMonitor>> m_vMonitors;
std::vector<std::unique_ptr<CLayerSurface>> m_vLayerSurfaces;
void createSeat(wl_seat*);
CLayerSurface* m_pLastSurface;
Vector2D m_vLastCoords;
void renderSurface(CLayerSurface*, bool forceInactive = false);
void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t, uint32_t);
void destroyBuffer(SPoolBuffer*);
int createPoolFile(size_t, std::string&);
bool setCloexec(const int&);
void recheckACK();
void initKeyboard();
void initMouse();
void sendFrame(CLayerSurface*);
SP<SPoolBuffer> getBufferForLS(CLayerSurface*);
SPoolBuffer* getBufferForLS(CLayerSurface*);
void convertBuffer(SPoolBuffer*);
void convertBuffer(SP<SPoolBuffer>);
void* convert24To32Buffer(SP<SPoolBuffer>);
void markDirty();
@ -68,4 +78,4 @@ class CHyprpicker {
private:
};
inline std::unique_ptr<CHyprpicker> g_pHyprpicker;
inline std::unique_ptr<CHyprpicker> g_pHyprpicker;

View file

@ -4,44 +4,38 @@
#include <deque>
#include <iostream>
#include <fstream>
#include <string.h>
#include <cstring>
#include <string>
#include <pthread.h>
#include <cmath>
#include <math.h>
#define class _class
#define namespace _namespace
#define static
#include "protocols/cursor-shape-v1.hpp"
#include "protocols/fractional-scale-v1.hpp"
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
#include "protocols/wlr-screencopy-unstable-v1.hpp"
#include "protocols/viewporter.hpp"
#include "protocols/wayland.hpp"
extern "C" {
#include "wlr-layer-shell-unstable-v1-protocol.h"
#include "wlr-screencopy-unstable-v1-protocol.h"
#include "xdg-shell-protocol.h"
#include <wayland-client.h>
#include <wayland-cursor.h>
}
#undef class
#undef namespace
#undef static
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <assert.h>
#include <cassert>
#include <cairo.h>
#include <cairo/cairo.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <cstdlib>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#include <algorithm>
#include <filesystem>
#include <thread>
#include <unordered_map>
#include <hyprutils/memory/WeakPtr.hpp>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer

View file

@ -4,14 +4,20 @@
#include "hyprpicker.hpp"
static void help(void) {
static void help() {
std::cout << "Hyprpicker usage: hyprpicker [arg [...]].\n\nArguments:\n"
<< " -a | --autocopy | Automatically copies the output to the clipboard (requires wl-clipboard)\n"
<< " -f | --format=fmt | Specifies the output format (cmyk, hex, rgb, hsl, hsv)\n"
<< " -n | --no-fancy | Disables the \"fancy\" (aka. colored) outputting\n"
<< " -h | --help | Show this help message\n"
<< " -r | --render-inactive | Render (freeze) inactive displays\n"
<< " -z | --no-zoom | Disable the zoom lens\n";
<< " -a | --autocopy | Automatically copies the output to the clipboard (requires wl-clipboard)\n"
<< " -f | --format=fmt | Specifies the output format (cmyk, hex, rgb, hsl, hsv)\n"
<< " -n | --no-fancy | Disables the \"fancy\" (aka. colored) outputting\n"
<< " -h | --help | Show this help message\n"
<< " -r | --render-inactive | Render (freeze) inactive displays\n"
<< " -z | --no-zoom | Disable the zoom lens\n"
<< " -q | --quiet | Disable most logs (leaves errors)\n"
<< " -v | --verbose | Enable more logs\n"
<< " -t | --no-fractional | Disable fractional scaling support\n"
<< " -d | --disable-hex-preview | Disable live preview of Hex code\n"
<< " -l | --lowercase-hex | Outputs the hexcode in lowercase\n"
<< " -V | --version | Print version info\n";
}
int main(int argc, char** argv, char** envp) {
@ -19,15 +25,21 @@ int main(int argc, char** argv, char** envp) {
while (true) {
int option_index = 0;
static struct option long_options[] = {{"autocopy", no_argument, NULL, 'a'},
{"format", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"no-fancy", no_argument, NULL, 'n'},
{"render-inactive", no_argument, NULL, 'r'},
{"no-zoom", no_argument, NULL, 'z'},
{NULL, 0, NULL, 0}};
static struct option long_options[] = {{"autocopy", no_argument, nullptr, 'a'},
{"format", required_argument, nullptr, 'f'},
{"help", no_argument, nullptr, 'h'},
{"no-fancy", no_argument, nullptr, 'n'},
{"render-inactive", no_argument, nullptr, 'r'},
{"no-zoom", no_argument, nullptr, 'z'},
{"no-fractional", no_argument, nullptr, 't'},
{"quiet", no_argument, nullptr, 'q'},
{"verbose", no_argument, nullptr, 'v'},
{"disable-hex-preview", no_argument, nullptr, 'd'},
{"lowercase-hex", no_argument, nullptr, 'l'},
{"version", no_argument, nullptr, 'V'},
{nullptr, 0, nullptr, 0}};
int c = getopt_long(argc, argv, ":f:hnarz", long_options, &option_index);
int c = getopt_long(argc, argv, ":f:hnarzqvtdlV", long_options, &option_index);
if (c == -1)
break;
@ -53,6 +65,15 @@ int main(int argc, char** argv, char** envp) {
case 'a': g_pHyprpicker->m_bAutoCopy = true; break;
case 'r': g_pHyprpicker->m_bRenderInactive = true; break;
case 'z': g_pHyprpicker->m_bNoZoom = true; break;
case 't': g_pHyprpicker->m_bNoFractional = true; break;
case 'q': Debug::quiet = true; break;
case 'v': Debug::verbose = true; break;
case 'd': g_pHyprpicker->m_bDisableHexPreview = true; break;
case 'l': g_pHyprpicker->m_bUseLowerCase = true; break;
case 'V': {
std::cout << "hyprpicker v" << HYPRPICKER_VERSION << "\n";
exit(0);
}
default: help(); exit(1);
}