diff --git a/scripts/sunset.py b/scripts/sunset.py new file mode 100644 index 0000000..26e72d3 --- /dev/null +++ b/scripts/sunset.py @@ -0,0 +1,88 @@ +#!/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)) diff --git a/scripts/sunset.sh b/scripts/sunset.sh deleted file mode 100755 index fb25f57..0000000 --- a/scripts/sunset.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/env sh - -SUNRISE_START="$((5 * 60 * 60))" # When to start turning up temperature -SUNRISE_END="$((7 * 60 * 60))" # When to reach max temperature - -SUNSET_START="$((21 * 60 * 60))" # When to start turning down temperature -SUNSET_END="$((23 * 60 * 60))" # When to reach min temperature - -DAY_TEMP="6000" # Display temperature (K) to use in full daylight -NIGHT_TEMP="2400" # Display temperature (K) to use at night - -CURRENT="$(((($(date '+%-H') * 60) + $(date '+%-M')) * 60 + $(date '+%-S')))" # Time in seconds since start of day - -# Performs a calculation using an argument containing an input string -calc() { - echo "scale=4; $1" | bc -} - -# Evaluates a boolean expression on an argument containing an input string -bool() { - echo "scale=1; $1" | bc -} - -# GLSL Smoothstep, takes a single number as an argument -smoothstep() { - if [ "$(bool "$1 <= 0")" = "1" ]; then - echo 0 - elif [ "$(bool "$1 >= 1")" = "1" ]; then - echo 1 - else - calc "$1 * $1 * (3 - 2 * $1)" - fi -} - -# Interpolates between the 4th and 5th arguments based on the value of the 2nd in relation to the 1st and 3rd -interpolate() { - LOWER_IN="$1" - VALUE="$2" - UPPER_IN="$3" - LOWER_OUT="$4" - UPPER_OUT="$5" - - if [ "$((VALUE <= LOWER_IN))" = "1" ]; then - echo "$LOWER_OUT" - elif [ "$((VALUE < UPPER_IN))" = "1" ]; then - calc "$LOWER_OUT + (($UPPER_OUT - $LOWER_OUT) * $(smoothstep "$(calc "($VALUE - $LOWER_IN) / ($UPPER_IN - $LOWER_IN)")"))" - else - echo "$UPPER_OUT" - fi -} - -if [ "$((CURRENT <= SUNRISE_START))" = "1" ]; then - TEMP="$NIGHT_TEMP" -elif [ "$((CURRENT < SUNRISE_END))" = "1" ]; then - TEMP="$(interpolate "$SUNRISE_START" "$CURRENT" "$SUNRISE_END" "$NIGHT_TEMP" "$DAY_TEMP")" -elif [ "$((CURRENT <= SUNSET_START))" = "1" ]; then - TEMP="$DAY_TEMP" -elif [ "$((CURRENT < SUNSET_END))" = "1" ]; then - TEMP="$(interpolate "$SUNSET_START" "$CURRENT" "$SUNSET_END" "$DAY_TEMP" "$NIGHT_TEMP")" -else - TEMP="$NIGHT_TEMP" -fi - -# TODO: Figure out a nice way to wait until Hyprland has properly started up before running this -hyprctl hyprsunset temperature "$TEMP" diff --git a/systemd/sunset.service b/systemd/sunset.service index 4fa5f55..c6d5b2f 100644 --- a/systemd/sunset.service +++ b/systemd/sunset.service @@ -3,4 +3,4 @@ Description=Sets the monitor temperature based on the time of day [Service] Type=oneshot -ExecStart=/home/mbradley/scripts/sunset.sh +ExecStart=/home/mbradley/scripts/sunset.py