-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor in preparation for IPC * Add IPC support * `clang-format` * Resolve comments * Allow querying value via IPC * Adjustable gamma clamp * Resolve review comments
- Loading branch information
Showing
6 changed files
with
452 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
#include "Hyprsunset.hpp" | ||
|
||
// kindly borrowed from https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html | ||
static Mat3x3 matrixForKelvin(unsigned long long temp) { | ||
float r = 1.F, g = 1.F, b = 1.F; | ||
|
||
temp /= 100; | ||
|
||
if (temp <= 66) { | ||
r = 255; | ||
g = std::clamp(99.4708025861 * std::log(temp) - 161.1195681661, 0.0, 255.0); | ||
if (temp <= 19) | ||
b = 0; | ||
else | ||
b = std::clamp(std::log(temp - 10) * 138.5177312231 - 305.0447927307, 0.0, 255.0); | ||
} else { | ||
r = std::clamp(329.698727446 * (std::pow(temp - 60, -0.1332047592)), 0.0, 255.0); | ||
g = std::clamp(288.1221695283 * (std::pow(temp - 60, -0.0755148492)), 0.0, 255.0); | ||
b = 255; | ||
} | ||
|
||
return std::array<float, 9>{r / 255.F, 0, 0, 0, g / 255.F, 0, 0, 0, b / 255.F}; | ||
} | ||
|
||
static void sigHandler(int sig) { | ||
if (g_pHyprsunset->state.pCTMMgr) // reset the CTM state... | ||
g_pHyprsunset->state.pCTMMgr.reset(); | ||
|
||
Debug::log(NONE, "┣ Exiting on user interrupt\n╹"); | ||
|
||
exit(0); | ||
} | ||
|
||
void SOutput::applyCTM(struct SState* state) { | ||
auto arr = state->ctm.getMatrix(); | ||
state->pCTMMgr->sendSetCtmForOutput(output->resource(), wl_fixed_from_double(arr[0]), wl_fixed_from_double(arr[1]), wl_fixed_from_double(arr[2]), wl_fixed_from_double(arr[3]), | ||
wl_fixed_from_double(arr[4]), wl_fixed_from_double(arr[5]), wl_fixed_from_double(arr[6]), wl_fixed_from_double(arr[7]), | ||
wl_fixed_from_double(arr[8])); | ||
} | ||
|
||
void CHyprsunset::commitCTMs() { | ||
g_pHyprsunset->state.pCTMMgr->sendCommit(); | ||
} | ||
|
||
int CHyprsunset::calculateMatrix() { | ||
if (KELVIN < 1000 || KELVIN > 20000) { | ||
Debug::log(NONE, "✖ Temperature invalid: {}. The temperature has to be between 1000 and 20000K", KELVIN); | ||
return 0; | ||
} | ||
|
||
if (GAMMA < 0 || GAMMA > MAX_GAMMA) { | ||
Debug::log(NONE, "✖ Gamma invalid: {}%. The gamma has to be between 0% and {}%", GAMMA * 100, MAX_GAMMA * 100); | ||
return 0; | ||
} | ||
|
||
if (!identity) | ||
Debug::log(NONE, "┣ Setting the temperature to {}K{}\n┃", KELVIN, kelvinSet ? "" : " (default)"); | ||
else | ||
Debug::log(NONE, "┣ Resetting the matrix (--identity passed)\n┃", KELVIN, kelvinSet ? "" : " (default)"); | ||
|
||
// calculate the matrix | ||
state.ctm = identity ? Mat3x3::identity() : matrixForKelvin(KELVIN); | ||
state.ctm.multiply(std::array<float, 9>{GAMMA, 0, 0, 0, GAMMA, 0, 0, 0, GAMMA}); | ||
|
||
Debug::log(NONE, "┣ Calculated the CTM to be {}\n┃", state.ctm.toString()); | ||
|
||
return 1; | ||
} | ||
|
||
int CHyprsunset::init() { | ||
// connect to the wayland server | ||
if (const auto SERVER = getenv("XDG_CURRENT_DESKTOP"); SERVER) | ||
Debug::log(NONE, "┣ Running on {}", SERVER); | ||
|
||
state.wlDisplay = wl_display_connect(nullptr); | ||
|
||
if (!state.wlDisplay) { | ||
Debug::log(NONE, "✖ Couldn't connect to a wayland compositor", KELVIN); | ||
return 0; | ||
} | ||
|
||
signal(SIGINT, sigHandler); | ||
signal(SIGTERM, sigHandler); | ||
|
||
state.pRegistry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.wlDisplay)); | ||
state.pRegistry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) { | ||
const std::string IFACE = interface; | ||
|
||
if (IFACE == hyprland_ctm_control_manager_v1_interface.name) { | ||
Debug::log(NONE, "┣ Found hyprland-ctm-control-v1 supported with version {}, binding to v1", version); | ||
state.pCTMMgr = makeShared<CCHyprlandCtmControlManagerV1>( | ||
(wl_proxy*)wl_registry_bind((wl_registry*)state.pRegistry->resource(), name, &hyprland_ctm_control_manager_v1_interface, 1)); | ||
} else if (IFACE == wl_output_interface.name) { | ||
|
||
if (std::find_if(state.outputs.begin(), state.outputs.end(), [name](const auto& el) { return el->id == name; }) != state.outputs.end()) | ||
return; | ||
|
||
Debug::log(NONE, "┣ Found new output with ID {}, binding", name); | ||
auto o = state.outputs.emplace_back( | ||
makeShared<SOutput>(makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)state.pRegistry->resource(), name, &wl_output_interface, 1)), name)); | ||
|
||
if (state.initialized) { | ||
Debug::log(NONE, "┣ already initialized, applying CTM instantly", name); | ||
o->applyCTM(&state); | ||
commitCTMs(); | ||
} | ||
} | ||
}); | ||
|
||
wl_display_roundtrip(state.wlDisplay); | ||
|
||
if (!state.pCTMMgr) { | ||
Debug::log(NONE, "✖ Compositor doesn't support hyprland-ctm-control-v1, are you running on Hyprland?", KELVIN); | ||
return 0; | ||
} | ||
|
||
Debug::log(NONE, "┣ Found {} outputs, applying CTMs", state.outputs.size()); | ||
|
||
for (auto& o : state.outputs) { | ||
o->applyCTM(&state); | ||
} | ||
|
||
commitCTMs(); | ||
|
||
state.initialized = true; | ||
|
||
g_pIPCSocket = std::make_unique<CIPCSocket>(); | ||
g_pIPCSocket->initialize(); | ||
|
||
while (wl_display_dispatch(state.wlDisplay) != -1) { | ||
std::lock_guard<std::mutex> lg(m_mtTickMutex); | ||
tick(); | ||
} | ||
|
||
return 1; | ||
} | ||
|
||
void CHyprsunset::tick() { | ||
if (g_pIPCSocket && g_pIPCSocket->mainThreadParseRequest()) { | ||
// Reload | ||
calculateMatrix(); | ||
|
||
for (auto& o : state.outputs) { | ||
o->applyCTM(&state); | ||
} | ||
|
||
commitCTMs(); | ||
|
||
wl_display_flush(state.wlDisplay); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#include <iostream> | ||
#include <cmath> | ||
#include <algorithm> | ||
#include <sys/signal.h> | ||
#include <wayland-client.h> | ||
#include <vector> | ||
#include "protocols/hyprland-ctm-control-v1.hpp" | ||
#include "protocols/wayland.hpp" | ||
|
||
#include "helpers/Log.hpp" | ||
|
||
#include "InstanceLock.hpp" | ||
|
||
#include "IPCSocket.hpp" | ||
#include <mutex> | ||
|
||
#include <hyprutils/math/Mat3x3.hpp> | ||
#include <hyprutils/memory/WeakPtr.hpp> | ||
using namespace Hyprutils::Math; | ||
using namespace Hyprutils::Memory; | ||
#define SP CSharedPointer | ||
#define WP CWeakPointer | ||
|
||
struct SOutput { | ||
SP<CCWlOutput> output; | ||
uint32_t id = 0; | ||
void applyCTM(struct SState*); | ||
}; | ||
|
||
struct SState { | ||
SP<CCWlRegistry> pRegistry; | ||
SP<CCHyprlandCtmControlManagerV1> pCTMMgr; | ||
wl_display* wlDisplay = nullptr; | ||
std::vector<SP<SOutput>> outputs; | ||
bool initialized = false; | ||
Mat3x3 ctm; | ||
CInstanceLock instLock; | ||
}; | ||
|
||
class CHyprsunset { | ||
public: | ||
float MAX_GAMMA = 1.0f; // default | ||
float GAMMA = 1.0f; // default | ||
unsigned long long KELVIN = 6000; // default | ||
bool kelvinSet = false, identity = false; | ||
SState state; | ||
std::mutex m_mtTickMutex; | ||
|
||
int calculateMatrix(); | ||
int applySettings(); | ||
int init(); | ||
void tick(); | ||
|
||
private: | ||
static void commitCTMs(); | ||
}; | ||
|
||
inline std::unique_ptr<CHyprsunset> g_pHyprsunset; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
#include "IPCSocket.hpp" | ||
#include "Hyprsunset.hpp" | ||
|
||
#include <cerrno> | ||
#include <cstdio> | ||
#include <cstdlib> | ||
#include <cstring> | ||
#include <netinet/in.h> | ||
#include <sys/socket.h> | ||
#include <sys/stat.h> | ||
#include <sys/types.h> | ||
#include <sys/un.h> | ||
#include <unistd.h> | ||
#include <pwd.h> | ||
#include <thread> | ||
|
||
void CIPCSocket::initialize() { | ||
std::thread([&]() { | ||
const auto SOCKET = socket(AF_UNIX, SOCK_STREAM, 0); | ||
|
||
if (SOCKET < 0) { | ||
Debug::log(ERR, "Couldn't start the hyprsunset Socket. (1) IPC will not work."); | ||
return; | ||
} | ||
|
||
sockaddr_un SERVERADDRESS = {.sun_family = AF_UNIX}; | ||
|
||
const auto HISenv = getenv("HYPRLAND_INSTANCE_SIGNATURE"); | ||
const auto RUNTIMEdir = getenv("XDG_RUNTIME_DIR"); | ||
const std::string USERID = std::to_string(getpwuid(getuid())->pw_uid); | ||
|
||
const auto USERDIR = RUNTIMEdir ? RUNTIMEdir + std::string{"/hypr/"} : "/run/user/" + USERID + "/hypr/"; | ||
|
||
std::string socketPath = HISenv ? USERDIR + std::string(HISenv) + "/.hyprsunset.sock" : USERDIR + ".hyprsunset.sock"; | ||
|
||
if (!HISenv) | ||
mkdir(USERDIR.c_str(), S_IRWXU); | ||
|
||
unlink(socketPath.c_str()); | ||
|
||
strcpy(SERVERADDRESS.sun_path, socketPath.c_str()); | ||
|
||
bind(SOCKET, (sockaddr*)&SERVERADDRESS, SUN_LEN(&SERVERADDRESS)); | ||
|
||
// 10 max queued. | ||
listen(SOCKET, 10); | ||
|
||
sockaddr_in clientAddress = {}; | ||
socklen_t clientSize = sizeof(clientAddress); | ||
|
||
char readBuffer[1024] = {0}; | ||
|
||
Debug::log(LOG, "hyprsunset socket started at {} (fd: {})", socketPath, SOCKET); | ||
while (1) { | ||
const auto ACCEPTEDCONNECTION = accept(SOCKET, (sockaddr*)&clientAddress, &clientSize); | ||
if (ACCEPTEDCONNECTION < 0) { | ||
Debug::log(ERR, "Couldn't listen on the hyprsunset Socket. (3) IPC will not work."); | ||
break; | ||
} else { | ||
do { | ||
Debug::log(LOG, "Accepted incoming socket connection request on fd {}", ACCEPTEDCONNECTION); | ||
std::lock_guard<std::mutex> lg(g_pHyprsunset->m_mtTickMutex); | ||
|
||
auto messageSize = read(ACCEPTEDCONNECTION, readBuffer, 1024); | ||
readBuffer[messageSize == 1024 ? 1023 : messageSize] = '\0'; | ||
if (messageSize == 0) | ||
break; | ||
std::string request(readBuffer); | ||
|
||
m_szRequest = request; | ||
m_bRequestReady = true; | ||
|
||
g_pHyprsunset->tick(); | ||
while (!m_bReplyReady) { // wait for Hyprsunset to finish processing the request | ||
std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
} | ||
write(ACCEPTEDCONNECTION, m_szReply.c_str(), m_szReply.length()); | ||
m_bReplyReady = false; | ||
m_szReply = ""; | ||
|
||
} while (1); | ||
Debug::log(LOG, "Closing Accepted Connection"); | ||
close(ACCEPTEDCONNECTION); | ||
} | ||
} | ||
|
||
close(SOCKET); | ||
}).detach(); | ||
} | ||
|
||
bool CIPCSocket::mainThreadParseRequest() { | ||
|
||
if (!m_bRequestReady) | ||
return false; | ||
|
||
std::string copy = m_szRequest; | ||
|
||
if (copy == "") | ||
return false; | ||
|
||
// now we can work on the copy | ||
|
||
Debug::log(LOG, "Received a request: {}", copy); | ||
|
||
// set default reply | ||
m_szReply = "ok"; | ||
m_bReplyReady = true; | ||
m_bRequestReady = false; | ||
|
||
// config commands | ||
if (copy.find("gamma") == 0) { | ||
int spaceSeparator = copy.find_first_of(' '); | ||
if (spaceSeparator == -1) { | ||
m_szReply = std::to_string(g_pHyprsunset->GAMMA * 100); | ||
return false; | ||
} | ||
|
||
std::string args = copy.substr(spaceSeparator + 1); | ||
float gamma = g_pHyprsunset->GAMMA * 100; | ||
float maxGamma = g_pHyprsunset->MAX_GAMMA * 100; | ||
if (args[0] == '+' || args[0] == '-') { | ||
try { | ||
if (args[0] == '-') | ||
gamma -= std::stof(args.substr(1)); | ||
else | ||
gamma += std::stof(args.substr(1)); | ||
} catch (std::exception& e) { | ||
m_szReply = "Invalid gamma value (should be in range 0-" + std::to_string(maxGamma) + "%)"; | ||
return false; | ||
} | ||
|
||
gamma = std::clamp(gamma, 0.0f, maxGamma); | ||
} else | ||
gamma = std::stof(args); | ||
|
||
if (gamma < 0 || gamma > maxGamma) { | ||
m_szReply = "Invalid gamma value (should be in range 0-" + std::to_string(maxGamma) + "%)"; | ||
return false; | ||
} | ||
|
||
g_pHyprsunset->GAMMA = gamma / 100; | ||
return true; | ||
} | ||
|
||
if (copy.find("temperature") == 0) { | ||
int spaceSeparator = copy.find_first_of(' '); | ||
if (spaceSeparator == -1) { | ||
m_szReply = std::to_string(g_pHyprsunset->KELVIN); | ||
return false; | ||
} | ||
|
||
std::string args = copy.substr(spaceSeparator + 1); | ||
unsigned long long kelvin = g_pHyprsunset->KELVIN; | ||
if (args[0] == '+' || args[0] == '-') { | ||
try { | ||
if (args[0] == '-') | ||
kelvin -= std::stoull(args.substr(1)); | ||
else | ||
kelvin += std::stoull(args.substr(1)); | ||
} catch (std::exception& e) { | ||
m_szReply = "Invalid temperature (should be in range 1000-20000)"; | ||
return false; | ||
} | ||
|
||
kelvin = std::clamp(kelvin, 1000ull, 20000ull); | ||
} else | ||
kelvin = std::stoull(args); | ||
|
||
if (kelvin < 1000 || kelvin > 20000) { | ||
m_szReply = "Invalid temperature (should be in range 1000-20000)"; | ||
return false; | ||
} | ||
|
||
g_pHyprsunset->KELVIN = kelvin; | ||
g_pHyprsunset->identity = false; | ||
return true; | ||
} | ||
|
||
if (copy.find("identity") == 0) { | ||
g_pHyprsunset->identity = true; | ||
return true; | ||
} | ||
|
||
m_szReply = "invalid command"; | ||
return false; | ||
} |
Oops, something went wrong.