mirror of
https://github.com/hyprwm/hyprsunset.git
synced 2025-05-12 21:30:41 +01:00
core: initial code commit
This commit is contained in:
parent
533081ab99
commit
f4c64db22a
7 changed files with 432 additions and 0 deletions
65
.clang-format
Normal file
65
.clang-format
Normal file
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: true
|
||||
AlignConsecutiveAssignments: true
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: AfterColon
|
||||
ColumnLimit: 180
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Preserve
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 4
|
||||
PointerAlignment: Left
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
|
||||
BraceWrapping:
|
||||
AfterEnum: false
|
||||
|
||||
AlignConsecutiveDeclarations: AcrossEmptyLines
|
||||
|
||||
NamespaceIndentation: All
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -30,3 +30,10 @@
|
|||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
|
||||
.cache
|
||||
.vscode
|
||||
build
|
||||
protocols/*.cpp
|
||||
protocols/*.hpp
|
137
CMakeLists.txt
Normal file
137
CMakeLists.txt
Normal file
|
@ -0,0 +1,137 @@
|
|||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
|
||||
string(STRIP ${VER_RAW} VERSION)
|
||||
|
||||
project(
|
||||
hyprsunset
|
||||
DESCRIPTION "An application to enable a blue-light filter on Hyprland"
|
||||
VERSION ${VERSION})
|
||||
|
||||
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
|
||||
|
||||
add_compile_definitions(HYPRSUNSET_VERSION="${VERSION}")
|
||||
|
||||
message(STATUS "Configuring hyprsunset!")
|
||||
|
||||
# Get git info hash and branch
|
||||
execute_process(
|
||||
COMMAND git rev-parse --abbrev-ref HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_BRANCH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND git rev-parse HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND bash -c "git show ${GIT_COMMIT_HASH} | head -n 5 | tail -n 1"
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_MESSAGE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND bash -c "git diff-index --quiet HEAD -- || echo \"dirty\""
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_DIRTY
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
#
|
||||
|
||||
include_directories(.)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
add_compile_options(
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
-Wno-unused-value
|
||||
-Wno-missing-field-initializers
|
||||
-Wno-narrowing
|
||||
-Wno-pointer-arith)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(
|
||||
deps
|
||||
REQUIRED
|
||||
IMPORTED_TARGET
|
||||
wayland-client
|
||||
wayland-protocols
|
||||
hyprutils>=0.2.3
|
||||
hyprwayland-scanner>=0.4.0)
|
||||
|
||||
file(GLOB_RECURSE SRCFILES "src/*.cpp")
|
||||
|
||||
add_executable(hyprsunset ${SRCFILES})
|
||||
|
||||
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 ${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(hyprsunset 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(hyprsunset PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
|
||||
endfunction()
|
||||
|
||||
protocolwayland()
|
||||
|
||||
pkg_check_modules(hyprland_protocols_dep REQUIRED IMPORTED_TARGET hyprland-protocols>=0.2.0)
|
||||
pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir)
|
||||
message(STATUS "hyprland-protocols dependency set to ${HYPRLAND_PROTOCOLS}")
|
||||
|
||||
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-ctm-control-v1" true)
|
||||
|
||||
target_compile_definitions(hyprsunset
|
||||
PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
|
||||
target_compile_definitions(hyprsunset PRIVATE "-DGIT_BRANCH=\"${GIT_BRANCH}\"")
|
||||
target_compile_definitions(
|
||||
hyprsunset PRIVATE "-DGIT_COMMIT_MESSAGE=\"${GIT_COMMIT_MESSAGE}\"")
|
||||
target_compile_definitions(hyprsunset PRIVATE "-DGIT_DIRTY=\"${GIT_DIRTY}\"")
|
||||
|
||||
target_link_libraries(hyprsunset rt)
|
||||
|
||||
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
||||
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
||||
include(CPack)
|
||||
|
||||
target_link_libraries(hyprsunset PkgConfig::deps)
|
||||
|
||||
target_link_libraries(hyprsunset 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(NOT DEFINED CMAKE_INSTALL_MANDIR)
|
||||
set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
|
||||
endif()
|
||||
|
||||
install(TARGETS hyprsunset)
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
0.1.0
|
0
protocols/.gitkeep
Normal file
0
protocols/.gitkeep
Normal file
30
src/helpers/Log.hpp
Normal file
30
src/helpers/Log.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <format>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
enum LogLevel {
|
||||
NONE = -1,
|
||||
LOG = 0,
|
||||
WARN,
|
||||
ERR,
|
||||
CRIT,
|
||||
INFO,
|
||||
TRACE
|
||||
};
|
||||
|
||||
namespace Debug {
|
||||
template <typename... Args>
|
||||
void log(LogLevel level, std::format_string<Args...> fmt, Args&&... args) {
|
||||
switch (level) {
|
||||
case NONE: break;
|
||||
case LOG: std::cout << "[LOG] "; break;
|
||||
case WARN: std::cout << "[WARN] "; break;
|
||||
case ERR: std::cout << "[ERR] "; break;
|
||||
case CRIT: std::cout << "[CRIT] "; break;
|
||||
case INFO: std::cout << "[INFO] "; break;
|
||||
case TRACE: std::cout << "[TRACE] "; break;
|
||||
}
|
||||
|
||||
std::cout << std::vformat(fmt.get(), std::make_format_args(args...)) << std::endl; // flush cuz systemd etc
|
||||
}
|
||||
}; // namespace Debug
|
192
src/main.cpp
Normal file
192
src/main.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
#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 <hyprutils/math/Mat3x3.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
// 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};
|
||||
}
|
||||
|
||||
struct SOutput {
|
||||
SP<CCWlOutput> output;
|
||||
uint32_t id = 0;
|
||||
void applyCTM();
|
||||
};
|
||||
|
||||
struct {
|
||||
SP<CCWlRegistry> pRegistry;
|
||||
SP<CCHyprlandCtmControlManagerV1> pCTMMgr;
|
||||
wl_display* wlDisplay = nullptr;
|
||||
std::vector<SP<SOutput>> outputs;
|
||||
bool initialized = false;
|
||||
Mat3x3 ctm;
|
||||
} state;
|
||||
|
||||
void sigHandler(int sig) {
|
||||
if (state.pCTMMgr) // reset the CTM state...
|
||||
state.pCTMMgr.reset();
|
||||
|
||||
Debug::log(NONE, "┣ Exiting on user interrupt\n╹");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void SOutput::applyCTM() {
|
||||
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]));
|
||||
}
|
||||
|
||||
static void commitCTMs() {
|
||||
state.pCTMMgr->sendCommit();
|
||||
}
|
||||
|
||||
static void printHelp() {
|
||||
Debug::log(NONE, "┣ --temperature -t → Set the temperature in K (default 6000)");
|
||||
Debug::log(NONE, "┣ --identity -i → Use the identity matrix (no color change)");
|
||||
Debug::log(NONE, "┣ --help -h → Print this info");
|
||||
Debug::log(NONE, "╹");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
Debug::log(NONE, "┏ hyprsunset v{} ━━╸\n┃", HYPRSUNSET_VERSION);
|
||||
|
||||
unsigned long long KELVIN = 6000; // default
|
||||
bool kelvinSet = false, identity = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (argv[i] == std::string{"-t"} || argv[i] == std::string{"--temperature"}) {
|
||||
if (i + 1 >= argc) {
|
||||
Debug::log(NONE, "✖ No temperature provided for {}", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
KELVIN = std::stoull(argv[i + 1]);
|
||||
kelvinSet = true;
|
||||
} catch (std::exception& e) {
|
||||
Debug::log(NONE, "✖ Temperature {} is not valid", argv[i + 1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
++i;
|
||||
} else if (argv[i] == std::string{"-i"} || argv[i] == std::string{"--identity"}) {
|
||||
identity = true;
|
||||
} else if (argv[i] == std::string{"-h"} || argv[i] == std::string{"--help"}) {
|
||||
printHelp();
|
||||
return 0;
|
||||
} else {
|
||||
Debug::log(NONE, "✖ Argument not recognized: {}", argv[i]);
|
||||
printHelp();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (KELVIN < 1000 || KELVIN > 20000) {
|
||||
Debug::log(NONE, "✖ Temperature invalid: {}. The temperature has to be between 1000 and 20000K", KELVIN);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Debug::log(NONE, "┣ Calculated the CTM to be {}\n┃", state.ctm.toString());
|
||||
|
||||
// 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 1;
|
||||
}
|
||||
|
||||
signal(SIGTERM, sigHandler);
|
||||
|
||||
state.pRegistry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.wlDisplay));
|
||||
state.pRegistry->setGlobal([](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();
|
||||
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 1;
|
||||
}
|
||||
|
||||
Debug::log(NONE, "┣ Found {} outputs, applying CTMs", state.outputs.size());
|
||||
|
||||
for (auto& o : state.outputs) {
|
||||
o->applyCTM();
|
||||
}
|
||||
|
||||
commitCTMs();
|
||||
|
||||
state.initialized = true;
|
||||
|
||||
while (wl_display_dispatch(state.wlDisplay) != -1) {
|
||||
;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue