Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 77 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,98 @@
# hyprpicker
<p align="center">
<img src="https://i.imgur.com/qggapUb.png" width="600" alt="Hyprpicker logo"/>
</p>

A wlroots-compatible Wayland color picker that does not suck.
<p align="center">
<img alt="Repo Size" src="https://img.shields.io/github/repo-size/r3b00thx/hyprpicker?style=for-the-badge&color=0181a4&labelColor=1B1B1B">
<img alt="Language" src="https://img.shields.io/badge/Language-C++17-00D2FF?style=for-the-badge&color=0181a4&labelColor=1B1B1B">
<img alt="License" src="https://img.shields.io/github/license/hyprwm/hyprpicker?style=for-the-badge&color=0181a4&labelColor=1B1B1B">
<img alt="Wayland" src="https://img.shields.io/badge/Wayland-wlroots-00D2FF?style=for-the-badge&color=0181a4&labelColor=1B1B1B">
</p>

![hyprpickerShort](https://user-images.githubusercontent.com/43317083/188224867-7d77a3b3-0a66-488c-8019-39b00060ab42.gif)
<p align="center"><b>A sleek, wlroots-native color picker for Wayland built for Hyprland with a focus on speed, accuracy, and simplicity.</b></p>

# Usage
<p align="center">
<img src="https://i.imgur.com/tTLCcOA.gif" alt="hyprpicker demo" width="600"/>
</p>

Launch it. Click. That's it.

## Options

See `hyprpicker --help`.
## ✨ Description

# Installation
**Hyprpicker** is a **blazing-fast, minimal, and accurate color picker** built for **Wayland compositors** like **Hyprland**.
It lets you instantly grab any color from your screen with pixel-perfect precision.

## Arch
Hyprpicker is designed to *just work* with clipboard copying, format selection, zoom lens, and desktop notifications.

`sudo pacman -S hyprpicker`

## Manual (Building)

Install dependencies:
- cmake
- pkg-config
- pango
- cairo
- wayland
- wayland-protocols
- hyprutils
- xkbcommon
## 🚀 Usage

Building is done via CMake:
**Run it, click anywhere, get the color.**
By default, the color is printed to stdout. Combine it with options like clipboard copy or notification for a smoother experience.

```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`
hyprpicker -a -n -b -f rgb
```

Install with:


## 🧰 Command-Line Options

```sh
cmake --install ./build
Hyprpicker usage: hyprpicker [arg [...]].

Arguments:
-a | --autocopy | Automatically copies the output to the clipboard (requires wl-clipboard)
-f | --format=fmt | Specifies the output format (cmyk, hex, rgb, hsl, hsv)
-n | --notify | Sends a desktop notification when a color is picked (requires notify-send and a notification daemon like dunst)
-b | --no-fancy | Disables the "fancy" (aka. colored) outputting
-h | --help | Show this help message
-r | --render-inactive | Render (freeze) inactive displays
-z | --no-zoom | Disable the zoom lens
-q | --quiet | Disable most logs (leaves errors)
-v | --verbose | Enable more logs
-t | --no-fractional | Disable fractional scaling support
-d | --disable-preview | Disable live preview of color
-l | --lowercase-hex | Outputs the hexcode in lowercase
-V | --version | Print version info
```

# Caveats

"Freezes" your displays when picking the color.

## 📦 Installation

```sh
sudo pacman -S hyprpicker
```



## 🔧 Build From Source

### Dependencies

- cmake
- pkg-config
- pango
- cairo
- wayland
- wayland-protocols
- hyprutils
- xkbcommon



### Build and install

```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -S . -B ./build
cmake --build ./build --config Release --target hyprpicker -j$(nproc)
sudo cmake --install ./build
```



## 📚 License

Hyprpicker is licensed under the BSD 3-Clause License — a permissive license that allows nearly unrestricted use, including commercial, as long as proper credit is given.
20 changes: 6 additions & 14 deletions src/clipboard/Clipboard.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
8 changes: 4 additions & 4 deletions src/clipboard/Clipboard.hpp
Original file line number Diff line number Diff line change
@@ -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);
};
1 change: 1 addition & 0 deletions src/defines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 61 additions & 34 deletions src/hyprpicker.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#include "hyprpicker.hpp"
#include "src/notify/Notify.hpp"
#include <csignal>
#include <cstddef>
#include <cstdio>
#include <format>

void sigHandler(int sig) {
static void sigHandler(int sig) {
g_pHyprpicker->m_vLayerSurfaces.clear();
exit(0);
}
Expand Down Expand Up @@ -242,13 +246,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);
}
Expand All @@ -262,7 +266,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));
Expand Down Expand Up @@ -292,39 +296,39 @@ 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};
}
}
} break;
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};
}
}
Expand Down Expand Up @@ -460,7 +464,7 @@ void CHyprpicker::renderSurface(CLayerSurface* pSurface, bool forceInactive) {
break;
};
};
cairo_set_source_rgba(PCAIRO, 0.0, 0.0, 0.0, 0.5);
cairo_set_source_rgba(PCAIRO, 0.0, 0.0, 0.0, 0.75);

double x, y, width = 8 + (11 * previewBuffer.length()), height = 28, radius = 6;

Expand Down Expand Up @@ -548,12 +552,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};
}
Expand Down Expand Up @@ -655,55 +660,70 @@ void CHyprpicker::initMouse() {
// 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;

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;
};

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::format("{}% {}% {}% {}%", c, m, y, k);

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);
NClipboard::copy(formattedColor);

if (m_bNotify)
NNotify::send(hexColor, formattedColor);

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());
NClipboard::copy(hexColor);

if (m_bNotify)
NNotify::send(hexColor, hexColor);

finish();
break;
}
case OUTPUT_RGB: {
std::string formattedColor = std::format("{} {} {}", COL.r, COL.g, COL.b);

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);
NClipboard::copy(formattedColor);

if (m_bNotify)
NNotify::send(hexColor, formattedColor);

finish();
break;
}
Expand All @@ -714,13 +734,20 @@ void CHyprpicker::initMouse() {
COL.getHSV(h, s, l_or_v);
else
COL.getHSL(h, s, l_or_v);

std::string formattedColor = std::format("{} {}% {}%", h, s, l_or_v);

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);
NClipboard::copy(formattedColor);

if (m_bNotify)
NNotify::send(hexColor, formattedColor);

finish();
break;
}
Expand Down
Loading