Compare commits

...

19 commits
v0.4.3 ... main

Author SHA1 Message Date
Sepandar
5ab0e1aaa4
input: add keyboard movement (#143) 2025-11-06 00:25:41 +00:00
Pavel Khromov
b645b892b1
wayland: Add null check for cursor shape device (#145)
Fixes segmentation fault when running on Wayland compositors that don't support the cursor_shape_v1 protocol (e.g., Wayfire).

The crash occurs in the pointer enter event handler when attempting to call m_pCursorShapeDevice->sendSetShape() without checking if the device was successfully initialized. On compositors without cursor_shape_v1 support, m_pCursorShapeDevice remains null, leading to a null pointer dereference.
2025-10-31 15:03:31 +00:00
Sepandar
cd7ba93fad
core: add custom format decoration (#142)
* added custom format option

* removed redundant switch

* made error message more specifc
2025-10-17 02:01:04 +02:00
Joel Eapen
b3f3f230c9
docs: change css example in man page to use hsl (#139) 2025-10-01 11:22:45 +01:00
DreamMaoMao
6f32582d22
core: add more flexible positioning and scaling (#138)
* feat: use axis scroller to change scale

* feat: add option -s to set scale and -u to set radius

* Optimize optarg conversion
2025-09-15 23:45:32 +02:00
d6a1363a86
CI/Nix: add cache-nix-action
Use nixbuild/nix-quick-install-action which pairs well with
nix-community/cache-nix-action.

Should help with build times by reducing the number of packages needing
to be re-downloaded on each run.

Parameters are taken from https://github.com/nix-community/cache-nix-action
and may be tweaked later.
2025-06-20 01:26:39 +03:00
Sepandar
166dce0fae
core: fix the zoom bubble showing without coords (#133) 2025-06-14 15:09:29 +02:00
R3B00T
68bc7875fd
core: add Notifications Support (#130)
new feature, a flag for notifications -n /
--notify. It's using the notify-send command line utility to send
desktop notifications. By default I made it compatible with dunst (you
will need to enable full markup in the dunst config) but it should work
with other notification daemons too.
2025-06-10 18:39:45 +02:00
Friday
b01491ac4e nix: use gcc15
also updated dependencies
2025-06-06 01:26:40 +03:00
Sepandar
500c46185d
core: Make preview format match output format (#123) 2025-05-18 18:03:24 +02:00
Zed
8f886f9e9b
README: update arch package (#126) 2025-05-17 14:30:02 +02:00
st0rmbtw
1efe0faea0
core: Exit with the code 2 when the user has cancelled picking a color (#125) 2025-05-17 14:27:52 +02:00
b0c0db66fa core: remove spammy log
fixes #122
2025-05-14 00:50:05 +02:00
980ebd486b
version: bump to 0.4.5 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
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
6692091d56 core: buffer handling fixes
fixes #101
2025-03-31 21:27:04 +01:00
21 changed files with 494 additions and 244 deletions

View file

@ -1,12 +1,12 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: file
FormatStyle: 'file'
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,

View file

@ -10,17 +10,41 @@ jobs:
uses: actions/checkout@v3
with:
submodules: recursive
- name: Install nix
uses: cachix/install-nix-action@v20
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
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
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 1G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
- uses: cachix/cachix-action@v16
with:
name: hyprland
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build Hyprpicker with default settings
run: nix build --print-build-logs --accept-flake-config

View file

@ -12,13 +12,13 @@ Launch it. Click. That's it.
See `hyprpicker --help`.
# Building
# Installation
## Arch
`yay -S hyprpicker-git`
`sudo pacman -S hyprpicker`
## Manual
## Manual (Building)
Install dependencies:
- cmake
@ -46,3 +46,4 @@ cmake --install ./build
# Caveats
"Freezes" your displays when picking the color.

View file

@ -1 +1 @@
0.4.3
0.4.5

View file

@ -73,7 +73,7 @@ Get a pixels color in HSL, wrapped in a CSS
.Fn hsl
function:
.Pp
.Dl $ hyprpicker -f hsl | sed 's/^/rgb(/; s/$/)/; y/ /,/'
.Dl $ hyprpicker -f hsl | sed 's/^/hsl(/; s/$/)/; y/ /,/'
.Sh SEE ALSO
.Xr hyprctl 1 ,
.Xr hyprland 1 ,

18
flake.lock generated
View file

@ -10,11 +10,11 @@
]
},
"locked": {
"lastModified": 1737632363,
"narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=",
"lastModified": 1749135356,
"narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "006620eb29d54ea9086538891404c78563d1bae1",
"rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb",
"type": "github"
},
"original": {
@ -33,11 +33,11 @@
]
},
"locked": {
"lastModified": 1735493474,
"narHash": "sha256-fktzv4NaqKm94VAkAoVqO/nqQlw+X0/tJJNAeCSfzK4=",
"lastModified": 1749145760,
"narHash": "sha256-IHaGWpGrv7seFWdw/1A+wHtTsPlOGIKMrk1TUIYJEFI=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "de913476b59ee88685fdc018e77b8f6637a2ae0b",
"rev": "817918315ea016cc2d94004bfb3223b5fd9dfcc6",
"type": "github"
},
"original": {
@ -48,11 +48,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1737469691,
"narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=",
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {

View file

@ -45,7 +45,7 @@
inputs.hyprwayland-scanner.overlays.default
(final: prev: {
hyprpicker = prev.callPackage ./nix/default.nix {
stdenv = prev.gcc14Stdenv;
stdenv = prev.gcc15Stdenv;
version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
};
hyprpicker-debug = final.hyprpicker.override {debug = true;};

View file

@ -1,20 +1,12 @@
#include "Clipboard.hpp"
#include "../includes.hpp"
#include <hyprutils/os/Process.hpp>
#include <string>
#include <vector>
void Clipboard::copy(const char* fmt, ...) {
char buf[CLIPBOARDMESSAGESIZE] = "";
char* outputStr;
void NClipboard::copy(std::string data) {
Hyprutils::OS::CProcess copy("wl-copy", {data});
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof buf, fmt, args);
va_end(args);
outputStr = strdup(buf);
if (fork() == 0)
execlp("wl-copy", "wl-copy", outputStr, NULL);
free(outputStr);
copy.runAsync();
}

View file

@ -1,7 +1,7 @@
#pragma once
#define CLIPBOARDMESSAGESIZE 24
#include <string>
namespace Clipboard {
void copy(const char* fmt, ...);
};
namespace NClipboard {
void copy(std::string data);
};

View file

@ -5,6 +5,7 @@
#include "helpers/Monitor.hpp"
#include "helpers/Color.hpp"
#include "clipboard/Clipboard.hpp"
#include "notify/Notify.hpp"
// git stuff
#ifndef GIT_COMMIT_HASH

80
src/helpers/Color.cpp Normal file
View file

@ -0,0 +1,80 @@
#include "Color.hpp"
#include <algorithm>
#include "../hyprpicker.hpp"
static float fmax3(float a, float b, float c) {
return (a > b && a > c) ? a : (b > c) ? b : c;
}
static float fmin3(float a, float b, float c) {
return (a < b && a < c) ? a : (b < c) ? b : c;
}
static bool floatEq(float a, float b) {
return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}
void CColor::getCMYK(float& c, float& m, float& y, float& k) const {
// http://www.codeproject.com/KB/applications/xcmyk.aspx
float rf = 1 - (r / 255.0f), gf = 1 - (g / 255.0f), bf = 1 - (b / 255.0f);
k = fmin3(rf, gf, bf);
float K = (k == 1) ? 1 : 1 - k;
c = (rf - k) / K;
m = (gf - k) / K;
y = (bf - k) / K;
c = std::round(c * 100);
m = std::round(m * 100);
y = std::round(y * 100);
k = std::round(k * 100);
}
void CColor::getHSV(float& h, float& s, float& v) const {
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
float rf = r / 255.0f, gf = g / 255.0f, bf = b / 255.0f;
float max = fmax3(rf, gf, bf), min = fmin3(rf, gf, bf);
float c = max - min;
v = max;
if (c == 0)
h = 0;
else if (v == rf)
h = 60 * (0 + (gf - bf) / c);
else if (v == gf)
h = 60 * (2 + (bf - rf) / c);
else /* v == bf */
h = 60 * (4 + (rf - gf) / c);
v = max;
s = floatEq(v, 0.0f) ? 0 : c / v;
h = std::round(h < 0 ? h + 360 : h);
v = std::round(v * 100);
s = std::round(s * 100);
}
void CColor::getHSL(float& h, float& s, float& l) const {
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
float rf = r / 255.0f, gf = g / 255.0f, bf = b / 255.0f, v;
float max = fmax3(rf, gf, bf), min = fmin3(rf, gf, bf);
float c = max - min;
v = max;
if (c == 0)
h = 0;
else if (v == rf)
h = 60 * (0 + (gf - bf) / c);
else if (v == gf)
h = 60 * (2 + (bf - rf) / c);
else /* v == bf */
h = 60 * (4 + (rf - gf) / c);
v = max;
s = floatEq(v, 0.0f) ? 0 : c / v;
l = (max + min) / 2;
s = (floatEq(l, 0.0f) || floatEq(l, 1.0f)) ? 0 : (v - l) / std::min(l, 1 - l);
h = std::round(h < 0 ? h + 360 : h);
s = std::round(s * 100);
l = std::round(l * 100);
}

View file

@ -5,4 +5,7 @@
class CColor {
public:
uint8_t r = 0, g = 0, b = 0, a = 0;
};
void getCMYK(float& c, float& m, float& y, float& k) const;
void getHSV(float& h, float& s, float& v) const;
void getHSL(float& h, float& s, float& l) const;
};

View file

@ -66,8 +66,7 @@ CLayerSurface::~CLayerSurface() {
static void onCallbackDone(CLayerSurface* surf, uint32_t when) {
surf->frameCallback.reset();
if (surf->dirty || !surf->rendered)
g_pHyprpicker->renderSurface(g_pHyprpicker->m_pLastSurface);
g_pHyprpicker->renderSurface(surf);
}
void CLayerSurface::sendFrame() {
@ -77,6 +76,8 @@ void CLayerSurface::sendFrame() {
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);
@ -85,20 +86,11 @@ void CLayerSurface::sendFrame() {
pSurface->sendSetBufferScale(m_pMonitor->scale);
pSurface->sendCommit();
dirty = false;
}
void CLayerSurface::markDirty() {
frameCallback = makeShared<CCWlCallback>(pSurface->sendFrame());
frameCallback->setDone([this](CCWlCallback* r, uint32_t when) { onCallbackDone(this, when); });
pSurface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
if (buffers[lastBuffer])
pSurface->sendAttach(buffers[lastBuffer]->buffer.get(), 0, 0);
pSurface->sendCommit();
dirty = true;
}

View file

@ -118,4 +118,4 @@ void SMonitor::initSCFrame() {
Debug::log(CRIT, "Failed to get a Screencopy!");
g_pHyprpicker->finish(1);
});
}
}

View file

@ -14,11 +14,11 @@ struct SMonitor {
SP<CCWlOutput> output = nullptr;
uint32_t wayland_name = 0;
Vector2D size;
int scale;
int32_t scale;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
bool ready = false;
CLayerSurface* pLS = nullptr;
SP<CCZwlrScreencopyFrameV1> pSCFrame = nullptr;
};
};

View file

@ -1,7 +1,16 @@
#include "hyprpicker.hpp"
#include "src/debug/Log.hpp"
#include "src/notify/Notify.hpp"
#include <csignal>
#include <cstddef>
#include <cstdio>
#include <format>
#include <hyprutils/math/Vector2D.hpp>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#include <xkbcommon/xkbcommon.h>
void sigHandler(int sig) {
static void sigHandler(int sig) {
g_pHyprpicker->m_vLayerSurfaces.clear();
exit(0);
}
@ -9,7 +18,7 @@ void sigHandler(int sig) {
void CHyprpicker::init() {
m_pXKBContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!m_pXKBContext)
Debug::log(ERR, "Failed to create xkb context");
Debug::log(ERR, "Failed to create xkb context, keyboard movement not supported");
m_pWLDisplay = wl_display_connect(nullptr);
@ -145,6 +154,116 @@ void CHyprpicker::finish(int code) {
exit(code);
}
void CHyprpicker::outputColor() {
// 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;
std::string hexColor = std::format("#{0:02x}{1:02x}{2:02x}", COL.r, COL.g, COL.b);
switch (m_bSelectedOutputMode) {
case OUTPUT_CMYK: {
float c, m, y, k;
COL.getCMYK(c, m, y, k);
std::string formattedColor = std::vformat(m_sOutputFormat, std::make_format_args(c, m, y, k));
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im%s\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, formattedColor.c_str());
else
Debug::log(NONE, formattedColor.c_str());
if (m_bAutoCopy)
NClipboard::copy(formattedColor);
if (m_bNotify)
NNotify::send(hexColor, formattedColor);
finish();
break;
}
case OUTPUT_HEX: {
std::string rHex, gHex, bHex;
if (m_bUseLowerCase) {
rHex = std::format("{:02x}", COL.r);
gHex = std::format("{:02x}", COL.g);
bHex = std::format("{:02x}", COL.b);
} else {
rHex = std::format("{:02X}", COL.r);
gHex = std::format("{:02X}", COL.g);
bHex = std::format("{:02X}", COL.b);
}
std::string formattedColor = std::vformat(m_sOutputFormat, std::make_format_args(rHex, gHex, bHex));
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im%s\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, formattedColor.c_str());
else
Debug::log(NONE, formattedColor.c_str());
if (m_bAutoCopy)
NClipboard::copy(hexColor);
if (m_bNotify)
NNotify::send(hexColor, hexColor);
finish();
break;
}
case OUTPUT_RGB: {
std::string formattedColor = std::vformat(m_sOutputFormat, std::make_format_args(COL.r, COL.g, COL.b));
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im%s\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, formattedColor.c_str());
else
Debug::log(NONE, formattedColor.c_str());
if (m_bAutoCopy)
NClipboard::copy(formattedColor);
if (m_bNotify)
NNotify::send(hexColor, formattedColor);
finish();
break;
}
case OUTPUT_HSL:
case OUTPUT_HSV: {
float h, s, l_or_v;
if (m_bSelectedOutputMode == OUTPUT_HSV)
COL.getHSV(h, s, l_or_v);
else
COL.getHSL(h, s, l_or_v);
std::string formattedColor = std::vformat(m_sOutputFormat, std::make_format_args(h, s, l_or_v));
if (m_bFancyOutput)
Debug::log(NONE, "\033[38;2;%i;%i;%i;48;2;%i;%i;%im%s\033[0m", FG, FG, FG, COL.r, COL.g, COL.b, formattedColor.c_str());
else
Debug::log(NONE, formattedColor.c_str());
if (m_bAutoCopy)
NClipboard::copy(formattedColor);
if (m_bNotify)
NNotify::send(hexColor, formattedColor);
finish();
break;
}
}
finish();
}
void CHyprpicker::recheckACK() {
for (auto& ls : m_vLayerSurfaces) {
if ((ls->wantsACK || ls->wantsReload) && ls->screenBuffer) {
@ -242,13 +361,13 @@ void CHyprpicker::convertBuffer(SP<SPoolBuffer> pBuffer) {
for (int y = 0; y < pBuffer->pixelSize.y; ++y) {
for (int x = 0; x < pBuffer->pixelSize.x; ++x) {
struct pixel {
struct SPixel {
// 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));
}* px = (struct SPixel*)(data + (static_cast<ptrdiff_t>(y * (int)pBuffer->pixelSize.x * 4)) + (static_cast<ptrdiff_t>(x * 4)));
std::swap(px->red, px->blue);
}
@ -262,7 +381,7 @@ void CHyprpicker::convertBuffer(SP<SPoolBuffer> pBuffer) {
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));
uint32_t* px = (uint32_t*)(data + (static_cast<ptrdiff_t>(y * (int)pBuffer->pixelSize.x * 4)) + (static_cast<ptrdiff_t>(x * 4)));
// conv to 8 bit
uint8_t R = (uint8_t)std::round((255.0 * (((*px) & 0b00000000000000000000001111111111) >> 0) / 1023.0));
@ -292,19 +411,19 @@ void* CHyprpicker::convert24To32Buffer(SP<SPoolBuffer> pBuffer) {
case WL_SHM_FORMAT_BGR888: {
for (int y = 0; y < pBuffer->pixelSize.y; ++y) {
for (int x = 0; x < pBuffer->pixelSize.x; ++x) {
struct pixel3 {
struct SPixel3 {
// little-endian RGB
unsigned char blue;
unsigned char green;
unsigned char red;
}* srcPx = (struct pixel3*)(oldBuffer + (y * pBuffer->stride) + (x * 3));
struct pixel4 {
}* srcPx = (struct SPixel3*)(oldBuffer + (static_cast<size_t>(y * pBuffer->stride)) + (static_cast<ptrdiff_t>(x * 3)));
struct SPixel4 {
// little-endian ARGB
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* dstPx = (struct pixel4*)(newBuffer + (y * newBufferStride) + (x * 4));
}* dstPx = (struct SPixel4*)(newBuffer + (static_cast<ptrdiff_t>(y * newBufferStride)) + (static_cast<ptrdiff_t>(x * 4)));
*dstPx = {.blue = srcPx->red, .green = srcPx->green, .red = srcPx->blue, .alpha = 0xFF};
}
}
@ -312,19 +431,19 @@ void* CHyprpicker::convert24To32Buffer(SP<SPoolBuffer> pBuffer) {
case WL_SHM_FORMAT_RGB888: {
for (int y = 0; y < pBuffer->pixelSize.y; ++y) {
for (int x = 0; x < pBuffer->pixelSize.x; ++x) {
struct pixel3 {
struct SPixel3 {
// big-endian RGB
unsigned char red;
unsigned char green;
unsigned char blue;
}* srcPx = (struct pixel3*)(oldBuffer + (y * pBuffer->stride) + (x * 3));
struct pixel4 {
}* srcPx = (struct SPixel3*)(oldBuffer + (y * pBuffer->stride) + (x * 3));
struct SPixel4 {
// big-endian ARGB
unsigned char alpha;
unsigned char red;
unsigned char green;
unsigned char blue;
}* dstPx = (struct pixel4*)(newBuffer + (y * newBufferStride) + (x * 4));
}* dstPx = (struct SPixel4*)(newBuffer + (y * newBufferStride) + (x * 4));
*dstPx = {.alpha = 0xFF, .red = srcPx->red, .green = srcPx->green, .blue = srcPx->blue};
}
}
@ -341,6 +460,7 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
const auto PBUFFER = getBufferForLS(pSurface);
if (!PBUFFER || !pSurface->screenBuffer) {
// Spammy log, doesn't matter.
// Debug::log(ERR, PBUFFER ? "renderSurface: pSurface->screenBuffer null" : "renderSurface: PBUFFER null");
return;
}
@ -359,7 +479,7 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
cairo_rectangle(PCAIRO, 0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
cairo_fill(PCAIRO);
if (pSurface == m_pLastSurface && !forceInactive) {
if (pSurface == m_pLastSurface && !forceInactive && m_bCoordsInitialized) {
const auto SCALEBUFS = pSurface->screenBuffer->pixelSize / PBUFFER->pixelSize;
const auto MOUSECOORDSABS = m_vLastCoords.floor() / pSurface->m_pMonitor->size;
const auto CLICKPOS = MOUSECOORDSABS * PBUFFER->pixelSize;
@ -401,7 +521,7 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
cairo_scale(PCAIRO, 1, 1);
cairo_arc(PCAIRO, CLICKPOS.x, CLICKPOS.y, 105 / SCALEBUFS.x, 0, 2 * M_PI);
cairo_arc(PCAIRO, CLICKPOS.x, CLICKPOS.y, m_iCircleRadius + 5 / SCALEBUFS.x, 0, 2 * M_PI);
cairo_clip(PCAIRO);
cairo_fill(PCAIRO);
@ -417,25 +537,58 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
cairo_matrix_t matrix;
cairo_matrix_init_identity(&matrix);
cairo_matrix_translate(&matrix, CLICKPOSBUF.x + 0.5f, CLICKPOSBUF.y + 0.5f);
cairo_matrix_scale(&matrix, 0.1f, 0.1f);
cairo_matrix_scale(&matrix, 1.0 / m_fZoomScale, 1.0 / m_fZoomScale);
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, CLICKPOS.x, CLICKPOS.y, 100 / SCALEBUFS.x, 0, 2 * M_PI);
cairo_arc(PCAIRO, CLICKPOS.x, CLICKPOS.y, m_iCircleRadius / SCALEBUFS.x, 0, 2 * M_PI);
cairo_clip(PCAIRO);
cairo_paint(PCAIRO);
if (!m_bDisableHexPreview) {
if (!m_bDisablePreview) {
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);
std::string previewBuffer;
switch (m_bSelectedOutputMode) {
case OUTPUT_HEX: {
std::string rHex, gHex, bHex;
if (m_bUseLowerCase) {
rHex = std::format("{:02x}", currentColor.r);
gHex = std::format("{:02x}", currentColor.g);
bHex = std::format("{:02x}", currentColor.b);
} else {
rHex = std::format("{:02X}", currentColor.r);
gHex = std::format("{:02X}", currentColor.g);
bHex = std::format("{:02X}", currentColor.b);
}
previewBuffer = std::vformat(m_sOutputFormat, std::make_format_args(rHex, gHex, bHex));
break;
};
case OUTPUT_RGB: {
previewBuffer = std::vformat(m_sOutputFormat, std::make_format_args(currentColor.r, currentColor.g, currentColor.b));
break;
};
case OUTPUT_HSL: {
float h, s, l;
currentColor.getHSL(h, s, l);
previewBuffer = std::vformat(m_sOutputFormat, std::make_format_args(h, s, l));
break;
};
case OUTPUT_HSV: {
float h, s, v;
currentColor.getHSV(h, s, v);
previewBuffer = std::vformat(m_sOutputFormat, std::make_format_args(h, s, v));
break;
};
case OUTPUT_CMYK: {
float c, m, y, k;
currentColor.getCMYK(c, m, y, k);
previewBuffer = std::vformat(m_sOutputFormat, std::make_format_args(c, m, y, k));
break;
};
};
cairo_set_source_rgba(PCAIRO, 0.0, 0.0, 0.0, 0.75);
cairo_set_source_rgba(PCAIRO, 0.0, 0.0, 0.0, 0.5);
double x, y, width = 85, height = 28, radius = 6;
double x, y, width = 8 + (11 * previewBuffer.length()), height = 28, radius = 6;
if (CLICKPOS.y > (PBUFFER->pixelSize.y - 50) && CLICKPOS.x > (PBUFFER->pixelSize.x - 100)) {
x = CLICKPOS.x - 80;
@ -450,7 +603,7 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
x = CLICKPOS.x;
y = CLICKPOS.y + 20;
}
x -= 5.5 * previewBuffer.length();
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);
@ -476,19 +629,19 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
else
cairo_move_to(PCAIRO, textX, CLICKPOS.y + 40);
cairo_show_text(PCAIRO, hexBuffer.c_str());
cairo_show_text(PCAIRO, previewBuffer.c_str());
cairo_surface_flush(PBUFFER->surface);
}
cairo_restore(PCAIRO);
cairo_pattern_destroy(PATTERN);
}
} else if (!m_bRenderInactive) {
} else if (!m_bRenderInactive && m_bCoordsInitialized) {
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba(PCAIRO, 0, 0, 0, 0);
cairo_rectangle(PCAIRO, 0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
cairo_fill(PCAIRO);
} else {
} else if (m_bCoordsInitialized) {
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);
@ -521,12 +674,13 @@ CColor CHyprpicker::getColorFromPixel(CLayerSurface* pLS, Vector2D pix) {
return CColor{.r = 0, .g = 0, .b = 0, .a = 0};
void* dataSrc = pLS->screenBuffer->paddedData ? pLS->screenBuffer->paddedData : pLS->screenBuffer->data;
struct pixel {
struct SPixel {
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char alpha;
}* px = (struct pixel*)((char*)dataSrc + ((ptrdiff_t)pix.y * (int)pLS->screenBuffer->pixelSize.x * 4) + ((ptrdiff_t)pix.x * 4));
}* px = (struct SPixel*)((char*)dataSrc + ((ptrdiff_t)pix.y * (int)pLS->screenBuffer->pixelSize.x * 4) + ((ptrdiff_t)pix.x * 4));
return CColor{.r = px->red, .g = px->green, .b = px->blue, .a = px->alpha};
}
@ -567,12 +721,22 @@ void CHyprpicker::initKeyboard() {
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();
int32_t XKBKey = xkb_state_key_get_one_sym(m_pXKBState, key + 8);
if (XKBKey == XKB_KEY_Right)
m_vLastCoords.x += m_vLastCoords.x < m_pLastSurface->m_pMonitor->size.x;
else if (XKBKey == XKB_KEY_Left)
m_vLastCoords.x -= m_vLastCoords.x > 0;
else if (XKBKey == XKB_KEY_Up)
m_vLastCoords.y -= m_vLastCoords.y > 0;
else if (XKBKey == XKB_KEY_Down)
m_vLastCoords.y += m_vLastCoords.y < m_pLastSurface->m_pMonitor->size.y;
else if (XKBKey == XKB_KEY_Return)
outputColor();
else if (XKBKey == XKB_KEY_Escape)
finish(2);
} else if (key == 1) // Assume keycode 1 is escape
finish();
finish(2);
});
}
@ -581,9 +745,8 @@ void CHyprpicker::initMouse() {
auto x = wl_fixed_to_double(surface_x);
auto y = wl_fixed_to_double(surface_y);
m_vLastCoords = {x, y};
markDirty();
m_vLastCoords = {x, y};
m_bCoordsInitialized = true;
for (auto& ls : m_vLayerSurfaces) {
if (ls->pSurface->resource() == surface) {
@ -592,14 +755,21 @@ void CHyprpicker::initMouse() {
}
}
m_pCursorShapeDevice->sendSetShape(serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR);
if (m_pCursorShapeDevice)
m_pCursorShapeDevice->sendSetShape(serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR);
markDirty();
});
m_pPointer->setLeave([this](CCWlPointer* r, uint32_t timeMs, wl_proxy* surf) {
m_pPointer->setLeave([this](CCWlPointer* r, uint32_t timeMs, wl_proxy* surface) {
for (auto& ls : m_vLayerSurfaces) {
if (ls->pSurface->resource() == surf) {
renderSurface(ls.get(), true);
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);
@ -609,134 +779,18 @@ void CHyprpicker::initMouse() {
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; };
m_pPointer->setButton([this](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) { outputColor(); });
m_pPointer->setAxis([this](CCWlPointer* r, uint32_t time, uint32_t axis, wl_fixed_t value) {
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
return;
// 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); };
double delta = wl_fixed_to_double(value);
// 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;
if (delta < 0)
m_fZoomScale = std::min(m_fZoomScale + 1.0, 100.0);
else
m_fZoomScale = std::max(m_fZoomScale - 1.0, 1.0);
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();
markDirty();
});
}

View file

@ -4,14 +4,17 @@
#include "helpers/LayerSurface.hpp"
#include "helpers/PoolBuffer.hpp"
enum eOutputMode {
// OUTPUT_COUNT is being used to count the number of output formats, it should always be last in the enum
enum eOutputMode : uint8_t {
OUTPUT_CMYK = 0,
OUTPUT_HEX,
OUTPUT_RGB,
OUTPUT_HSL,
OUTPUT_HSV
OUTPUT_HSV,
};
const std::array<uint8_t, 5> numOutputValues = {4, 3, 3, 3, 3};
class CHyprpicker {
public:
void init();
@ -37,17 +40,21 @@ class CHyprpicker {
xkb_state* m_pXKBState = nullptr;
eOutputMode m_bSelectedOutputMode = OUTPUT_HEX;
std::string m_sOutputFormat = "";
bool m_bFancyOutput = true;
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_bAutoCopy = false;
bool m_bNotify = false;
bool m_bRenderInactive = false;
bool m_bNoZoom = false;
bool m_bNoFractional = false;
bool m_bDisablePreview = false;
bool m_bUseLowerCase = false;
bool m_bRunning = true;
bool m_bRunning = true;
float m_fZoomScale = 10.0;
int m_iCircleRadius = 100;
std::vector<std::unique_ptr<SMonitor>> m_vMonitors;
std::vector<std::unique_ptr<CLayerSurface>> m_vLayerSurfaces;
@ -55,6 +62,7 @@ class CHyprpicker {
CLayerSurface* m_pLastSurface;
Vector2D m_vLastCoords;
bool m_bCoordsInitialized = false;
void renderSurface(CLayerSurface*, bool forceInactive = false);
@ -72,6 +80,7 @@ class CHyprpicker {
void markDirty();
void finish(int code = 0);
void outputColor();
CColor getColorFromPixel(CLayerSurface*, Vector2D);

View file

@ -35,7 +35,9 @@
#include <unordered_map>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/os/Process.hpp>
using namespace Hyprutils::Memory;
using namespace Hyprutils::OS;
#define SP CSharedPointer
#define WP CWeakPointer

View file

@ -1,22 +1,30 @@
#include <cstdint>
#include <format>
#include <regex>
#include <strings.h>
#include <iostream>
#include "hyprpicker.hpp"
#include "src/debug/Log.hpp"
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"
<< " -o | --output-format=fmt | Specifies how the output color should be formatted e.g. rgb({0}, {1}, {2}) would output rgb(red, green, blue) if --format=rgb\n"
<< " -n | --notify | Sends a desktop notification when a color is picked (requires notify-send and a notification daemon like dunst)\n"
<< " -b | --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"
<< " -d | --disable-preview | Disable live preview of color\n"
<< " -l | --lowercase-hex | Outputs the hexcode in lowercase\n"
<< " -s | --scale=scale | Set the zoom scale (between 1 and 10)\n"
<< " -u | --radius=radius | Set the circle radius (between 1 and 1000)\n"
<< " -V | --version | Print version info\n";
}
@ -27,19 +35,23 @@ int main(int argc, char** argv, char** envp) {
int option_index = 0;
static struct option long_options[] = {{"autocopy", no_argument, nullptr, 'a'},
{"format", required_argument, nullptr, 'f'},
{"output-format", required_argument, nullptr, 'o'},
{"help", no_argument, nullptr, 'h'},
{"no-fancy", no_argument, nullptr, 'n'},
{"no-fancy", no_argument, nullptr, 'b'},
{"notify", 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'},
{"disable-preview", no_argument, nullptr, 'd'},
{"lowercase-hex", no_argument, nullptr, 'l'},
{"version", no_argument, nullptr, 'V'},
{"scale", required_argument, nullptr, 's'},
{"radius", required_argument, nullptr, 'u'},
{nullptr, 0, nullptr, 0}};
int c = getopt_long(argc, argv, ":f:hnarzqvtdlV", long_options, &option_index);
int c = getopt_long(argc, argv, ":f:o:hnbarzqvtdlVs:u:", long_options, &option_index);
if (c == -1)
break;
@ -60,21 +72,57 @@ int main(int argc, char** argv, char** envp) {
exit(1);
}
break;
case 'o': g_pHyprpicker->m_sOutputFormat = optarg; break;
case 'h': help(); exit(0);
case 'n': g_pHyprpicker->m_bFancyOutput = false; break;
case 'b': g_pHyprpicker->m_bFancyOutput = false; break;
case 'n': g_pHyprpicker->m_bNotify = true; break;
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 'd': g_pHyprpicker->m_bDisablePreview = true; break;
case 'l': g_pHyprpicker->m_bUseLowerCase = true; break;
case 'V': {
std::cout << "hyprpicker v" << HYPRPICKER_VERSION << "\n";
exit(0);
}
case 's': {
float value;
auto result = std::from_chars(optarg, optarg + strlen(optarg), value);
if (result.ec != std::errc() || result.ptr != optarg + strlen(optarg)) {
std::cerr << "Invalid scale value: " << optarg << "\n";
exit(1);
}
if (value < 1.0f || value > 10.0f) {
std::cerr << "Scale must be between 1 and 10!\n";
exit(1);
}
g_pHyprpicker->m_fZoomScale = value;
break;
}
case 'u': {
int value;
auto result = std::from_chars(optarg, optarg + strlen(optarg), value);
if (result.ec != std::errc() || result.ptr != optarg + strlen(optarg)) {
std::cerr << "Invalid radius value: " << optarg << "\n";
exit(1);
}
if (value < 1 || value > 1000) {
std::cerr << "Radius must be between 1 and 1000!\n";
exit(1);
}
g_pHyprpicker->m_iCircleRadius = value;
break;
}
default: help(); exit(1);
}
}
@ -82,6 +130,23 @@ int main(int argc, char** argv, char** envp) {
if (!isatty(fileno(stdout)) || getenv("NO_COLOR"))
g_pHyprpicker->m_bFancyOutput = false;
if (g_pHyprpicker->m_sOutputFormat.empty()) {
switch (g_pHyprpicker->m_bSelectedOutputMode) {
case OUTPUT_CMYK: g_pHyprpicker->m_sOutputFormat = "{}% {}% {}% {}%"; break;
case OUTPUT_HEX: g_pHyprpicker->m_sOutputFormat = "#{}{}{}"; break;
case OUTPUT_RGB: g_pHyprpicker->m_sOutputFormat = "{} {} {}"; break;
case OUTPUT_HSL: g_pHyprpicker->m_sOutputFormat = "{} {}% {}%"; break;
case OUTPUT_HSV: g_pHyprpicker->m_sOutputFormat = "{} {}% {}%"; break;
}
}
try {
std::array<uint8_t, 4> dummy = {0, 0, 0, 0};
(void)std::vformat(g_pHyprpicker->m_sOutputFormat, std::make_format_args(dummy[0], dummy[1], dummy[2], dummy[3]));
} catch (const std::format_error& e) {
Debug::log(NONE, "Invalid --output-format: %s", e.what());
exit(1);
}
g_pHyprpicker->init();
return 0;

19
src/notify/Notify.cpp Normal file
View file

@ -0,0 +1,19 @@
#include "Notify.hpp"
#include "../includes.hpp"
#include <cstdint>
#include <cstdio>
#include <format>
#include <hyprutils/os/Process.hpp>
#include <iostream>
#include <string>
#include <hyprutils/os/Process.hpp>
void NNotify::send(std::string hexColor, std::string formattedColor) {
std::string notifyBody = std::format("<span>Selected color: <span color='{}'><b>{}</b></span></span>", hexColor, formattedColor);
Hyprutils::OS::CProcess notify("notify-send", {"-t", "5000", "-i", "color-select-symbolic", "Color Picker", notifyBody});
notify.runAsync();
}

8
src/notify/Notify.hpp Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include <cstdint>
#include <string>
namespace NNotify {
void send(std::string hexColor, std::string formattedColor);
}