pax_global_header00006660000000000000000000000064150700471400014510gustar00rootroot0000000000000052 comment=057feb7a724b7fc0f3a406d6db08b59734db006a hyprsunset-0.3.3/000077500000000000000000000000001507004714000137375ustar00rootroot00000000000000hyprsunset-0.3.3/.clang-format000066400000000000000000000034161507004714000163160ustar00rootroot00000000000000--- 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 hyprsunset-0.3.3/.github/000077500000000000000000000000001507004714000152775ustar00rootroot00000000000000hyprsunset-0.3.3/.github/workflows/000077500000000000000000000000001507004714000173345ustar00rootroot00000000000000hyprsunset-0.3.3/.github/workflows/nix-build.yml000066400000000000000000000031071507004714000217530ustar00rootroot00000000000000name: Build on: [push, pull_request, workflow_dispatch] jobs: nix: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Nix uses: nixbuild/nix-quick-install-action@v31 with: 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 # not needed (yet) # - uses: cachix/cachix-action@v12 # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build run: nix flake check --print-build-logs --keep-going hyprsunset-0.3.3/.gitignore000066400000000000000000000005041507004714000157260ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app .cache .vscode build protocols/*.cpp protocols/*.hpphyprsunset-0.3.3/CMakeLists.txt000066400000000000000000000113701507004714000165010ustar00rootroot00000000000000cmake_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!") configure_file(systemd/hyprsunset.service.in systemd/hyprsunset.service @ONLY) # 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 26) 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 hyprlang 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.4.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() include(GNUInstallDirs) install(TARGETS hyprsunset) pkg_get_variable(SYSTEMD_USER_UNIT_DIR systemd systemduserunitdir) if (NOT SYSTEMD_USER_UNIT_DIR) set(SYSTEMD_USER_UNIT_DIR "${CMAKE_INSTALL_PREFIX}/lib/systemd/user") endif() install(FILES ${CMAKE_BINARY_DIR}/systemd/hyprsunset.service DESTINATION "${SYSTEMD_USER_UNIT_DIR}") hyprsunset-0.3.3/LICENSE000066400000000000000000000027371507004714000147550ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2024, Hypr Development Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hyprsunset-0.3.3/README.md000066400000000000000000000005331507004714000152170ustar00rootroot00000000000000## hyprsunset An application to enable a blue-light filter on Hyprland > !IMPORTANT! > This app requires hyprland>=0.45.0 ## Docs / Configuration [See the wiki](https://wiki.hypr.land/Hypr-Ecosystem/hyprsunset/) ## Support This utility relies on support for `hyprland-ctm-control-v1` by the compositor, so it's most likely hyprland-exclusive. hyprsunset-0.3.3/VERSION000066400000000000000000000000051507004714000150020ustar00rootroot000000000000000.3.3hyprsunset-0.3.3/flake.lock000066400000000000000000000066231507004714000157020ustar00rootroot00000000000000{ "nodes": { "hyprland-protocols": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1749046714, "narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=", "owner": "hyprwm", "repo": "hyprland-protocols", "rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprland-protocols", "type": "github" } }, "hyprlang": { "inputs": { "hyprutils": [ "hyprutils" ], "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1750371198, "narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=", "owner": "hyprwm", "repo": "hyprlang", "rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1749135356, "narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=", "owner": "hyprwm", "repo": "hyprutils", "rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1749145760, "narHash": "sha256-IHaGWpGrv7seFWdw/1A+wHtTsPlOGIKMrk1TUIYJEFI=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "817918315ea016cc2d94004bfb3223b5fd9dfcc6", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1748929857, "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", "owner": "NixOS", "repo": "nixpkgs", "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "hyprland-protocols": "hyprland-protocols", "hyprlang": "hyprlang", "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" } } }, "root": "root", "version": 7 } hyprsunset-0.3.3/flake.nix000066400000000000000000000030051507004714000155370ustar00rootroot00000000000000{ description = "An application to enable a blue-light filter on Hyprland"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; hyprland-protocols = { url = "github:hyprwm/hyprland-protocols"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprlang = { url = "github:hyprwm/hyprlang"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; inputs.hyprutils.follows = "hyprutils"; }; hyprwayland-scanner = { url = "github:hyprwm/hyprwayland-scanner"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; }; outputs = { self, nixpkgs, systems, ... } @ inputs: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem (system: import nixpkgs { localSystem.system = system; overlays = with self.overlays; [default]; }); in { overlays = import ./nix/overlays.nix {inherit inputs lib;}; packages = eachSystem (system: { default = self.packages.${system}.hyprsunset; inherit (pkgsFor.${system}) hyprsunset; }); checks = eachSystem (system: self.packages.${system}); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; } hyprsunset-0.3.3/nix/000077500000000000000000000000001507004714000145355ustar00rootroot00000000000000hyprsunset-0.3.3/nix/default.nix000066400000000000000000000020271507004714000167020ustar00rootroot00000000000000{ lib, stdenv, cmake, pkg-config, hyprland-protocols, hyprutils, hyprlang, hyprwayland-scanner, wayland, wayland-protocols, wayland-scanner, version ? "git", }: let inherit (lib.sources) cleanSource cleanSourceWith; inherit (lib.strings) hasSuffix; in stdenv.mkDerivation { pname = "hyprsunset"; inherit version; src = cleanSourceWith { filter = name: _type: let baseName = baseNameOf (toString name); in ! (hasSuffix ".nix" baseName); src = cleanSource ../.; }; nativeBuildInputs = [ cmake pkg-config hyprwayland-scanner ]; buildInputs = [ hyprland-protocols hyprutils hyprlang wayland wayland-protocols wayland-scanner ]; meta = { homepage = "https://github.com/hyprwm/hyprsunset"; description = "An application to enable a blue-light filter on Hyprland"; license = lib.licenses.bsd3; platforms = lib.platforms.linux; mainProgram = "hyprsunset"; }; } hyprsunset-0.3.3/nix/overlays.nix000066400000000000000000000014301507004714000171170ustar00rootroot00000000000000{ lib, inputs, }: let mkDate = longDate: (lib.concatStringsSep "-" [ (builtins.substring 0 4 longDate) (builtins.substring 4 2 longDate) (builtins.substring 6 2 longDate) ]); version = lib.removeSuffix "\n" (builtins.readFile ../VERSION); in { default = inputs.self.overlays.hyprsunset; hyprsunset = lib.composeManyExtensions [ inputs.hyprland-protocols.overlays.default inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default (final: prev: { hyprsunset = prev.callPackage ./default.nix { stdenv = prev.gcc15Stdenv; version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); }; }) ]; } hyprsunset-0.3.3/protocols/000077500000000000000000000000001507004714000157635ustar00rootroot00000000000000hyprsunset-0.3.3/protocols/.gitkeep000066400000000000000000000000001507004714000174020ustar00rootroot00000000000000hyprsunset-0.3.3/src/000077500000000000000000000000001507004714000145265ustar00rootroot00000000000000hyprsunset-0.3.3/src/ConfigManager.cpp000066400000000000000000000071311507004714000177340ustar00rootroot00000000000000#include "ConfigManager.hpp" #include #include #include #include #include #include "helpers/Log.hpp" static std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hyprsunset"); return paths.first.value_or(""); } CConfigManager::CConfigManager(std::string configPath) : m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}) { currentConfigPath = configPath.empty() ? getMainConfigPath() : configPath; } void CConfigManager::init() { m_config.addConfigValue("max-gamma", Hyprlang::INT{100}); m_config.addSpecialCategory("profile", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("profile", "time", Hyprlang::STRING{"00:00"}); m_config.addSpecialConfigValue("profile", "temperature", Hyprlang::INT{6000}); m_config.addSpecialConfigValue("profile", "gamma", Hyprlang::FLOAT{1.0f}); m_config.addSpecialConfigValue("profile", "identity", Hyprlang::INT{0}); m_config.commence(); auto result = m_config.parse(); if (result.error) Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError()); } std::vector CConfigManager::getSunsetProfiles() { std::vector result; auto keys = m_config.listKeysForSpecialCategory("profile"); result.reserve(keys.size()); for (auto& key : keys) { std::string time; unsigned long temperature; float gamma; bool identity; try { time = std::any_cast(m_config.getSpecialConfigValue("profile", "time", key.c_str())); temperature = std::any_cast(m_config.getSpecialConfigValue("profile", "temperature", key.c_str())); gamma = std::any_cast(m_config.getSpecialConfigValue("profile", "gamma", key.c_str())); identity = std::any_cast(m_config.getSpecialConfigValue("profile", "identity", key.c_str())); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct Profile: {}", e.what()); // } catch (const std::out_of_range& e) { RASSERT(false, "Missing property for Profile: {}", e.what()); // } size_t separator = time.find(':'); if (separator == std::string::npos) RASSERT(false, "Invalid time format for profile {}", key); int hour = 0, minute = 0; try { hour = std::stoi(time.substr(0, separator)); minute = std::stoi(time.substr(separator + 1).c_str()); } catch (const std::exception& e) { Debug::log(ERR, "Invalid time format: {}, skipping this profile", time); continue; } // clang-format off result.push_back(SSunsetProfile{ .time = { .hour = std::chrono::hours(hour), .minute = std::chrono::minutes(minute), }, .temperature = temperature, .gamma = gamma, .identity = identity, }); // clang-format on } return result; } float CConfigManager::getMaxGamma() { try { return std::any_cast(m_config.getConfigValue("max-gamma")) / 100.f; } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct max-gamma: {}", e.what()); // } } hyprsunset-0.3.3/src/ConfigManager.hpp000066400000000000000000000006641507004714000177450ustar00rootroot00000000000000#pragma once #include "Hyprsunset.hpp" #include #include class CConfigManager { public: CConfigManager(std::string configPath); std::vector getSunsetProfiles(); float getMaxGamma(); void init(); private: Hyprlang::CConfig m_config; std::string currentConfigPath; }; inline UP g_pConfigManager; hyprsunset-0.3.3/src/Hyprsunset.cpp000066400000000000000000000310631507004714000174210ustar00rootroot00000000000000#include "ConfigManager.hpp" #include "helpers/Log.hpp" #include "IPCSocket.hpp" #include #include #include #include #include #include #include #define TIMESPEC_NSEC_PER_SEC 1000000000L static void registerSignalAction(int sig, void (*handler)(int), int sa_flags = 0) { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = sa_flags; sigaction(sig, &sa, nullptr); } static void handleExitSignal(int sig) { Debug::log(NONE, "┣ Exiting on user interrupt\n╹"); g_pHyprsunset->terminate(); } static void timespecAddNs(timespec* pTimespec, int64_t delta) { auto delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; auto delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; pTimespec->tv_sec += delta_s_high; pTimespec->tv_nsec += (long)delta_ns_low; if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC; ++pTimespec->tv_sec; } } // 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{r / 255.F, 0, 0, 0, g / 255.F, 0, 0, 0, b / 255.F}; } 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{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; } state.pRegistry = makeShared((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) { auto targetVersion = std::min(version, 2u); Debug::log(NONE, "┣ Found hyprland-ctm-control-v1 supported with version {}, binding to v{}", version, targetVersion); state.pCTMMgr = makeShared( (wl_proxy*)wl_registry_bind((wl_registry*)state.pRegistry->resource(), name, &hyprland_ctm_control_manager_v1_interface, targetVersion)); if (targetVersion >= 2) { state.pCTMMgr->setBlocked([](CCHyprlandCtmControlManagerV1*) { Debug::log(NONE, "✖ A CTM manager is already running on the current compositor."); exit(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(makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.pRegistry->resource(), name, &wl_output_interface, 3)), name)); if (state.initialized) { Debug::log(NONE, "┣ already initialized, applying CTM instantly", name); o->applyCTM(&state); commitCTMs(); } } }); state.pRegistry->setGlobalRemove([this](CCWlRegistry* r, uint32_t name) { std::erase_if(state.outputs, [name](const auto& e) { return e->id == name; }); }); 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 {} output(s), applying CTMs", state.outputs.size()); reload(); state.initialized = true; g_pIPCSocket = std::make_unique(); g_pIPCSocket->initialize(); registerSignalAction(SIGTERM, ::handleExitSignal); registerSignalAction(SIGINT, ::handleExitSignal); state.timerFD = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); schedule(); startEventLoop(); return 1; } void CHyprsunset::startEventLoop() { pollfd pollfds[] = { { .fd = wl_display_get_fd(state.wlDisplay), .events = POLLIN, }, { .fd = state.timerFD, .events = POLLIN, }, }; std::thread pollThread([&]() { while (1) { bool preparedToRead = wl_display_prepare_read(state.wlDisplay) == 0; int ret = 0; if (m_bTerminate) break; if (preparedToRead) { ret = poll(pollfds, 2, 5000); if (ret < 0) { RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno); wl_display_cancel_read(state.wlDisplay); continue; } for (size_t i = 0; i < 2; ++i) { RASSERT(!(pollfds[i].revents & POLLHUP), "[core] Disconnected from pollfd id {}", i); } wl_display_read_events(state.wlDisplay); } if (ret > 0 || !preparedToRead) { Debug::log(TRACE, "[core] got poll event"); std::lock_guard lg(m_sEventLoopInternals.loopRequestMutex); m_sEventLoopInternals.shouldProcess = true; m_sEventLoopInternals.loopSignal.notify_one(); } } }); while (1) { std::unique_lock lk(m_sEventLoopInternals.loopMutex); if (!m_sEventLoopInternals.shouldProcess) m_sEventLoopInternals.loopSignal.wait_for(lk, std::chrono::milliseconds(5000), [this] { return m_sEventLoopInternals.shouldProcess; }); if (m_bTerminate) break; std::lock_guard lg(m_sEventLoopInternals.loopRequestMutex); m_sEventLoopInternals.shouldProcess = false; if (pollfds[0].revents & POLLIN) { wl_display_dispatch_pending(state.wlDisplay); wl_display_flush(state.wlDisplay); } if (m_sEventLoopInternals.isScheduled) reload(); else tick(); m_sEventLoopInternals.isScheduled = false; } Debug::log(TRACE, "Exiting loop"); m_bTerminate = true; // cleanup wl resources state.outputs.clear(); state.pRegistry.reset(); state.pCTMMgr.reset(); timespec now; clock_gettime(CLOCK_MONOTONIC, &now); timespecAddNs(&now, 1000L * 1000L * 100L); itimerspec ts = {.it_value = now}; timerfd_settime(state.timerFD, TFD_TIMER_ABSTIME, &ts, nullptr); pollThread.join(); wl_display_disconnect(state.wlDisplay); close(state.timerFD); } void CHyprsunset::tick() { if (g_pIPCSocket && g_pIPCSocket->mainThreadParseRequest()) reload(); } void CHyprsunset::reload() { calculateMatrix(); for (auto& o : state.outputs) { o->applyCTM(&state); } commitCTMs(); wl_display_flush(state.wlDisplay); } void CHyprsunset::loadCurrentProfile() { profiles = g_pConfigManager->getSunsetProfiles(); Debug::log(NONE, "┣ Loaded {} profiles", profiles.size()); std::sort(profiles.begin(), profiles.end(), [](const auto& a, const auto& b) { if (a.time.hour < b.time.hour) return true; else if (a.time.hour > b.time.hour) return false; else return a.time.minute < b.time.minute; }); int current = g_pHyprsunset->currentProfile(); if (current == -1) return; SSunsetProfile profile = g_pHyprsunset->profiles[current]; KELVIN = profile.temperature; GAMMA = profile.gamma; identity = profile.identity; MAX_GAMMA = g_pConfigManager->getMaxGamma(); Debug::log(NONE, "┣ Applying profile from: {}:{}", profile.time.hour.count(), profile.time.minute.count()); } int CHyprsunset::currentProfile() { if (profiles.empty()) return -1; else if (profiles.size() == 1) return 0; auto now = std::chrono::zoned_time(std::chrono::current_zone(), std::chrono::system_clock::now()).get_local_time(); for (size_t i = 0; i < profiles.size(); ++i) { const auto& p = profiles[i]; auto time = std::chrono::floor(now) + p.time.hour + p.time.minute; if (time >= now) { if (i == 0) return profiles.size() - 1; return i - 1; } } return profiles.size() - 1; } void CHyprsunset::schedule() { std::thread([&]() { while (true) { int current = currentProfile(); if (current == -1) break; SSunsetProfile nextProfile = (size_t)current == profiles.size() - 1 ? profiles[0] : profiles[current + 1]; auto now = std::chrono::zoned_time(std::chrono::current_zone(), std::chrono::system_clock::now()).get_local_time(); auto time = std::chrono::floor(now) + nextProfile.time.hour + nextProfile.time.minute; if (now >= time) time += std::chrono::days(1); while (time >= std::chrono::zoned_time(std::chrono::current_zone(), std::chrono::system_clock::now()).get_local_time() + std::chrono::minutes(1)) std::this_thread::sleep_for(std::chrono::minutes(1)); auto system_time = std::chrono::zoned_time{std::chrono::current_zone(), time}.get_sys_time(); std::this_thread::sleep_until(system_time); int newcurrent = currentProfile(); if (newcurrent == -1) break; SSunsetProfile newProfile = profiles[newcurrent]; std::lock_guard lg(m_sEventLoopInternals.loopRequestMutex); KELVIN = newProfile.temperature; GAMMA = newProfile.gamma; identity = newProfile.identity; Debug::log(NONE, "┣ Switched to new profile from: {}:{}", newProfile.time.hour.count(), newProfile.time.minute.count()); m_sEventLoopInternals.shouldProcess = true; m_sEventLoopInternals.isScheduled = true; m_sEventLoopInternals.loopSignal.notify_all(); }; }).detach(); } void CHyprsunset::terminate() { m_sEventLoopInternals.shouldProcess = true; m_sEventLoopInternals.loopSignal.notify_all(); m_bTerminate = true; } hyprsunset-0.3.3/src/Hyprsunset.hpp000066400000000000000000000044141507004714000174260ustar00rootroot00000000000000#include #include #include #include #include #include #include "protocols/hyprland-ctm-control-v1.hpp" #include "protocols/wayland.hpp" #include #include using namespace Hyprutils::Math; using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer #define WP CWeakPointer struct SOutput { SP output; uint32_t id = 0; void applyCTM(struct SState*); }; struct SState { SP pRegistry; SP pCTMMgr; wl_display* wlDisplay = nullptr; std::vector> outputs; bool initialized = false; Mat3x3 ctm; int timerFD = -1; }; struct SSunsetProfile { struct { std::chrono::hours hour; std::chrono::minutes minute; } time; unsigned long temperature = 6000; float gamma = 1.0f; bool identity = false; }; 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; bool m_bTerminate = false; int calculateMatrix(); int init(); void tick(); void loadCurrentProfile(); void terminate(); struct { std::condition_variable loopSignal; std::mutex loopMutex; std::mutex loopRequestMutex; bool shouldProcess = false; bool isScheduled = false; } m_sEventLoopInternals; private: static void commitCTMs(); void reload(); void schedule(); int currentProfile(); void startEventLoop(); std::vector profiles; }; inline std::unique_ptr g_pHyprsunset; hyprsunset-0.3.3/src/IPCSocket.cpp000066400000000000000000000140771507004714000170270ustar00rootroot00000000000000#include "IPCSocket.hpp" #include "Hyprsunset.hpp" #include "helpers/Log.hpp" #include #include #include #include #include #include #include #include #include #include #include #include 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 lg(g_pHyprsunset->m_sEventLoopInternals.loopRequestMutex); 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; try { if (args[0] == '+' || args[0] == '-') { if (args[0] == '-') kelvin -= std::stoull(args.substr(1)); else kelvin += std::stoull(args.substr(1)); kelvin = std::clamp(kelvin, 1000ull, 20000ull); } else kelvin = std::stoull(args); } catch (std::exception& e) { m_szReply = "Invalid temperature (should be an integer in range 1000-20000)"; return false; } if (kelvin < 1000 || kelvin > 20000) { m_szReply = "Invalid temperature (should be an integer 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; } hyprsunset-0.3.3/src/IPCSocket.hpp000066400000000000000000000006261507004714000170270ustar00rootroot00000000000000#pragma once #include #include #include class CIPCSocket { public: void initialize(); bool mainThreadParseRequest(); private: std::mutex m_mtRequestMutex; std::string m_szRequest = ""; std::string m_szReply = ""; bool m_bRequestReady = false; bool m_bReplyReady = false; }; inline std::unique_ptr g_pIPCSocket; hyprsunset-0.3.3/src/helpers/000077500000000000000000000000001507004714000161705ustar00rootroot00000000000000hyprsunset-0.3.3/src/helpers/Log.hpp000066400000000000000000000040031507004714000174170ustar00rootroot00000000000000#pragma once #include #include #include #include enum LogLevel { NONE = -1, LOG = 0, WARN, ERR, CRIT, INFO, TRACE }; #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ std::format(reason, ##__VA_ARGS__), __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \ std::abort(); \ } namespace Debug { inline bool trace = false; template void log(LogLevel level, std::format_string fmt, Args&&... args) { if (!trace && (level == LOG || level == INFO)) return; 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 hyprsunset-0.3.3/src/main.cpp000066400000000000000000000100651507004714000161600ustar00rootroot00000000000000#include "ConfigManager.hpp" #include "src/helpers/Log.hpp" static void printHelp() { Debug::log(NONE, "┣ --gamma -g → Set the display gamma (default 100%)"); Debug::log(NONE, "┣ --gamma_max → Set the maximum display gamma (default 100%, maximum 200%)"); 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, "┣ --verbose → Print more logging"); Debug::log(NONE, "┣ --version -v → Print the version"); Debug::log(NONE, "┣ --help -h → Print this info"); Debug::log(NONE, "╹"); } int main(int argc, char** argv, char** envp) { std::string configPath; int kelvin = -1; float gamma = -1; float maxGamma = -1; bool identity = false; g_pHyprsunset = std::make_unique(); 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]); } catch (std::exception& e) { Debug::log(NONE, "✖ Temperature {} is not valid", argv[i + 1]); return 1; } ++i; } else if (argv[i] == std::string{"-g"} || argv[i] == std::string{"--gamma"}) { if (i + 1 >= argc) { Debug::log(NONE, "✖ No gamma provided for {}", argv[i]); return 1; } try { gamma = std::stof(argv[i + 1]) / 100; } catch (std::exception& e) { Debug::log(NONE, "✖ Gamma {} is not valid", argv[i + 1]); return 1; } ++i; } else if (argv[i] == std::string{"--gamma_max"}) { if (i + 1 >= argc) { Debug::log(NONE, "✖ No gamma provided for {}", argv[i]); return 1; } try { maxGamma = std::stof(argv[i + 1]) / 100; } catch (std::exception& e) { Debug::log(NONE, "✖ Maximum gamma {} 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{"-c"} || argv[i] == std::string{"--config"}) { if (i + 1 >= argc) { Debug::log(NONE, "✖ No config path provided for {}", argv[i]); return 1; } configPath = argv[i + 1]; } else if (argv[i] == std::string{"-h"} || argv[i] == std::string{"--help"}) { printHelp(); return 0; } else if (argv[i] == std::string{"-v"} || argv[i] == std::string{"--version"}) { Debug::log(NONE, "hyprsunset v{}", HYPRSUNSET_VERSION); return 0; } else if (argv[i] == std::string{"--verbose"}) { Debug::trace = true; } else { Debug::log(NONE, "✖ Argument not recognized: {}", argv[i]); printHelp(); return 1; } } Debug::log(NONE, "┏ hyprsunset v{} ━━╸\n┃", HYPRSUNSET_VERSION); g_pConfigManager = makeUnique(configPath); g_pConfigManager->init(); g_pHyprsunset->loadCurrentProfile(); if (kelvin != -1) { g_pHyprsunset->KELVIN = kelvin; g_pHyprsunset->kelvinSet = true; g_pHyprsunset->identity = false; } if (gamma != -1) g_pHyprsunset->GAMMA = gamma; if (maxGamma != -1) g_pHyprsunset->MAX_GAMMA = maxGamma; if (identity) g_pHyprsunset->identity = true; if (!g_pHyprsunset->calculateMatrix()) return 1; if (!g_pHyprsunset->init()) return 1; return 0; } hyprsunset-0.3.3/src/meson.build000066400000000000000000000025121507004714000166700ustar00rootroot00000000000000globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check : true) src = globber.stdout().strip().split('\n') executable('hyprsunset', src, dependencies : [ dependency('wayland-client'), dependency('wayland-cursor'), dependency('hyprlang'), dependency('hyprutils', version : '>= 0.2.3'), dependency('threads'), ], install : true, ) hyprsunset-0.3.3/systemd/000077500000000000000000000000001507004714000154275ustar00rootroot00000000000000hyprsunset-0.3.3/systemd/hyprsunset.service.in000066400000000000000000000006611507004714000216450ustar00rootroot00000000000000[Unit] Description=An application to enable a blue-light filter on Hyprland. Documentation=https://wiki.hyprland.org/Hypr-Ecosystem/hyprsunset/ PartOf=graphical-session.target Requires=graphical-session.target After=graphical-session.target ConditionEnvironment=WAYLAND_DISPLAY [Service] Type=simple ExecStart=@CMAKE_INSTALL_PREFIX@/bin/hyprsunset Slice=session.slice Restart=on-failure [Install] WantedBy=graphical-session.target