#!/bin/env python """Sets the monitor temperature based on the current time using Hyprsunset""" from datetime import datetime from os import environ from socket import AF_UNIX, SOCK_STREAM, socket def find_hyprsunset_socket() -> str: """Gets the socket file location""" xdg_runtime_directory = environ["XDG_RUNTIME_DIR"] hyprland_instance_signature = environ["HYPRLAND_INSTANCE_SIGNATURE"] return f"{xdg_runtime_directory}/hypr/{hyprland_instance_signature}/.hyprsunset.sock" def apply_temperature(temperature: float) -> None: """Uses IPC to tell Hyprsunset to update the monitor temperature""" hyprsunset = socket(AF_UNIX, SOCK_STREAM) hyprsunset.connect(find_hyprsunset_socket()) message = f"temperature {temperature:.0f}".encode() sent = 0 while sent < len(message): sent += hyprsunset.send(message[sent:]) type Range[T] = tuple[T, T] type LerpRange = Range[float] type HourRange = Range[int] type ElapsedRange = Range[float] type TemperatureRange = Range[float] def lerped_amount(x: float, edges: LerpRange) -> float: """How far `x` is between `values`""" return (x - edges[0]) / (edges[1] - edges[0]) def smoothstep(x: float, edges: LerpRange) -> float: """Smoothly interpolates between the `edges` based on `x`""" # Technically I should bounds check but whatever return (x * x * (3.0 - 2.0 * x)) * (edges[1] - edges[0]) + edges[0] def calculate_temperature(elapsed: float, sunrise: ElapsedRange, sunset: ElapsedRange, temperature: TemperatureRange) -> float: """Determines which temperature to set the monitors to""" if elapsed <= sunrise[0]: return temperature[0] elif elapsed < sunrise[1]: return smoothstep(lerped_amount(elapsed, sunrise), temperature) elif elapsed <= sunset[0]: return temperature[1] elif elapsed < sunset[1]: return smoothstep(lerped_amount(elapsed, sunrise), temperature[::-1]) else: return temperature[0] def day_elapsed(hours: int = 0, minutes: int = 0, seconds: int = 0) -> float: """Converts (H, M, S) into [0, 1] representing how far through the day it is""" TOTAL_SECONDS = 24 * 60 * 60 elapsed = (((hours * 60) + minutes) * 60) + seconds return elapsed / TOTAL_SECONDS def current_elapsed() -> float: """Time through the day represented in [0, 1]""" current = datetime.now() return day_elapsed(current.hour, current.minute, current.second) def to_elapsed(hours: HourRange) -> ElapsedRange: """Converts a range of hours into a range representing how far through the day is is""" return day_elapsed(hours[0]), day_elapsed(hours[1]) def main(sunrise: HourRange, sunset: HourRange, temperature: TemperatureRange) -> None: """Adjusts the monitor temperature based on the current time""" apply_temperature( calculate_temperature( current_elapsed(), to_elapsed(sunrise), to_elapsed(sunset), temperature ) ) if __name__ == "__main__": main((5, 7), (21, 23), (2500.0, 6000.0))