mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
gamma: switch to wlsunset-style transitions
This commit is contained in:
@@ -22,6 +22,8 @@ linters:
|
||||
- (*os.Process).Signal
|
||||
- (*os.Process).Kill
|
||||
- syscall.Kill
|
||||
# Seek on memfd (reset position before passing fd)
|
||||
- syscall.Seek
|
||||
# DBus cleanup
|
||||
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
||||
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
||||
|
||||
@@ -154,11 +154,12 @@ func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, co
|
||||
}
|
||||
|
||||
bind := keybinds.Keybind{
|
||||
Key: keyStr,
|
||||
Description: kb.Description,
|
||||
Action: rawAction,
|
||||
Subcategory: subcategory,
|
||||
Source: source,
|
||||
Key: keyStr,
|
||||
Description: kb.Description,
|
||||
Action: rawAction,
|
||||
Subcategory: subcategory,
|
||||
Source: source,
|
||||
HideOnOverlay: kb.HideOnOverlay,
|
||||
}
|
||||
|
||||
if source == "dms" && conflicts != nil {
|
||||
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
)
|
||||
|
||||
type NiriKeyBinding struct {
|
||||
Mods []string
|
||||
Key string
|
||||
Action string
|
||||
Args []string
|
||||
Description string
|
||||
Source string
|
||||
Mods []string
|
||||
Key string
|
||||
Action string
|
||||
Args []string
|
||||
Description string
|
||||
HideOnOverlay bool
|
||||
Source string
|
||||
}
|
||||
|
||||
type NiriSection struct {
|
||||
@@ -273,19 +274,26 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
|
||||
}
|
||||
|
||||
var description string
|
||||
var hideOnOverlay bool
|
||||
if node.Properties != nil {
|
||||
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
||||
description = val.ValueString()
|
||||
switch val.ValueString() {
|
||||
case "null", "":
|
||||
hideOnOverlay = true
|
||||
default:
|
||||
description = val.ValueString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &NiriKeyBinding{
|
||||
Mods: mods,
|
||||
Key: key,
|
||||
Action: action,
|
||||
Args: args,
|
||||
Description: description,
|
||||
Source: p.currentSource,
|
||||
Mods: mods,
|
||||
Key: key,
|
||||
Action: action,
|
||||
Args: args,
|
||||
Description: description,
|
||||
HideOnOverlay: hideOnOverlay,
|
||||
Source: p.currentSource,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package keybinds
|
||||
|
||||
type Keybind struct {
|
||||
Key string `json:"key"`
|
||||
Description string `json:"desc"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Subcategory string `json:"subcat,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Conflict *Keybind `json:"conflict,omitempty"`
|
||||
Key string `json:"key"`
|
||||
Description string `json:"desc"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Subcategory string `json:"subcat,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
HideOnOverlay bool `json:"hideOnOverlay,omitempty"`
|
||||
Conflict *Keybind `json:"conflict,omitempty"`
|
||||
}
|
||||
|
||||
type DMSBindsStatus struct {
|
||||
|
||||
@@ -238,9 +238,17 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
l := 0
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy != nil {
|
||||
e.Head = proxy.(*ZwlrOutputHeadV1)
|
||||
if proxy == nil {
|
||||
head := &ZwlrOutputHeadV1{}
|
||||
head.SetContext(i.Context())
|
||||
head.SetID(objectID)
|
||||
registerServerProxy(i.Context(), head, objectID)
|
||||
e.Head = head
|
||||
} else if head, ok := proxy.(*ZwlrOutputHeadV1); ok {
|
||||
e.Head = head
|
||||
} else {
|
||||
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||
// Replace it with the correct type
|
||||
head := &ZwlrOutputHeadV1{}
|
||||
head.SetContext(i.Context())
|
||||
head.SetID(objectID)
|
||||
@@ -715,9 +723,17 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
l := 0
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy != nil {
|
||||
e.Mode = proxy.(*ZwlrOutputModeV1)
|
||||
if proxy == nil {
|
||||
mode := &ZwlrOutputModeV1{}
|
||||
mode.SetContext(i.Context())
|
||||
mode.SetID(objectID)
|
||||
registerServerProxy(i.Context(), mode, objectID)
|
||||
e.Mode = mode
|
||||
} else if mode, ok := proxy.(*ZwlrOutputModeV1); ok {
|
||||
e.Mode = mode
|
||||
} else {
|
||||
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||
// Replace it with the correct type
|
||||
mode := &ZwlrOutputModeV1{}
|
||||
mode.SetContext(i.Context())
|
||||
mode.SetID(objectID)
|
||||
@@ -743,7 +759,26 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
}
|
||||
var e ZwlrOutputHeadV1CurrentModeEvent
|
||||
l := 0
|
||||
e.Mode = i.Context().GetProxy(client.Uint32(data[l : l+4])).(*ZwlrOutputModeV1)
|
||||
objectID := client.Uint32(data[l : l+4])
|
||||
proxy := i.Context().GetProxy(objectID)
|
||||
if proxy == nil {
|
||||
// Mode not yet registered, create it
|
||||
mode := &ZwlrOutputModeV1{}
|
||||
mode.SetContext(i.Context())
|
||||
mode.SetID(objectID)
|
||||
registerServerProxy(i.Context(), mode, objectID)
|
||||
e.Mode = mode
|
||||
} else if mode, ok := proxy.(*ZwlrOutputModeV1); ok {
|
||||
e.Mode = mode
|
||||
} else {
|
||||
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||
// Replace it with the correct type
|
||||
mode := &ZwlrOutputModeV1{}
|
||||
mode.SetContext(i.Context())
|
||||
mode.SetID(objectID)
|
||||
registerServerProxy(i.Context(), mode, objectID)
|
||||
e.Mode = mode
|
||||
}
|
||||
l += 4
|
||||
|
||||
i.currentModeHandler(e)
|
||||
|
||||
@@ -2,8 +2,6 @@ package wayland
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
type GammaRamp struct {
|
||||
@@ -12,6 +10,126 @@ type GammaRamp struct {
|
||||
Blue []uint16
|
||||
}
|
||||
|
||||
type rgb struct {
|
||||
r, g, b float64
|
||||
}
|
||||
|
||||
type xyz struct {
|
||||
x, y, z float64
|
||||
}
|
||||
|
||||
func illuminantD(temp int) (float64, float64, bool) {
|
||||
var x float64
|
||||
switch {
|
||||
case temp >= 2500 && temp <= 7000:
|
||||
t := float64(temp)
|
||||
x = 0.244063 + 0.09911e3/t + 2.9678e6/(t*t) - 4.6070e9/(t*t*t)
|
||||
case temp > 7000 && temp <= 25000:
|
||||
t := float64(temp)
|
||||
x = 0.237040 + 0.24748e3/t + 1.9018e6/(t*t) - 2.0064e9/(t*t*t)
|
||||
default:
|
||||
return 0, 0, false
|
||||
}
|
||||
y := -3*(x*x) + 2.870*x - 0.275
|
||||
return x, y, true
|
||||
}
|
||||
|
||||
func planckianLocus(temp int) (float64, float64, bool) {
|
||||
var x, y float64
|
||||
switch {
|
||||
case temp >= 1667 && temp <= 4000:
|
||||
t := float64(temp)
|
||||
x = -0.2661239e9/(t*t*t) - 0.2343589e6/(t*t) + 0.8776956e3/t + 0.179910
|
||||
if temp <= 2222 {
|
||||
y = -1.1064814*(x*x*x) - 1.34811020*(x*x) + 2.18555832*x - 0.20219683
|
||||
} else {
|
||||
y = -0.9549476*(x*x*x) - 1.37418593*(x*x) + 2.09137015*x - 0.16748867
|
||||
}
|
||||
case temp > 4000 && temp < 25000:
|
||||
t := float64(temp)
|
||||
x = -3.0258469e9/(t*t*t) + 2.1070379e6/(t*t) + 0.2226347e3/t + 0.240390
|
||||
y = 3.0817580*(x*x*x) - 5.87338670*(x*x) + 3.75112997*x - 0.37001483
|
||||
default:
|
||||
return 0, 0, false
|
||||
}
|
||||
return x, y, true
|
||||
}
|
||||
|
||||
func srgbGamma(value, gamma float64) float64 {
|
||||
if value <= 0.0031308 {
|
||||
return 12.92 * value
|
||||
}
|
||||
return math.Pow(1.055*value, 1.0/gamma) - 0.055
|
||||
}
|
||||
|
||||
func clamp01(v float64) float64 {
|
||||
switch {
|
||||
case v > 1.0:
|
||||
return 1.0
|
||||
case v < 0.0:
|
||||
return 0.0
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func xyzToSRGB(c xyz) rgb {
|
||||
return rgb{
|
||||
r: srgbGamma(clamp01(3.2404542*c.x-1.5371385*c.y-0.4985314*c.z), 2.2),
|
||||
g: srgbGamma(clamp01(-0.9692660*c.x+1.8760108*c.y+0.0415560*c.z), 2.2),
|
||||
b: srgbGamma(clamp01(0.0556434*c.x-0.2040259*c.y+1.0572252*c.z), 2.2),
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeRGB(c *rgb) {
|
||||
maxw := math.Max(c.r, math.Max(c.g, c.b))
|
||||
if maxw > 0 {
|
||||
c.r /= maxw
|
||||
c.g /= maxw
|
||||
c.b /= maxw
|
||||
}
|
||||
}
|
||||
|
||||
func calcWhitepoint(temp int) rgb {
|
||||
if temp == 6500 {
|
||||
return rgb{r: 1.0, g: 1.0, b: 1.0}
|
||||
}
|
||||
|
||||
var wp xyz
|
||||
|
||||
switch {
|
||||
case temp >= 25000:
|
||||
x, y, _ := illuminantD(25000)
|
||||
wp.x = x
|
||||
wp.y = y
|
||||
case temp >= 4000:
|
||||
x, y, _ := illuminantD(temp)
|
||||
wp.x = x
|
||||
wp.y = y
|
||||
case temp >= 2500:
|
||||
x1, y1, _ := illuminantD(temp)
|
||||
x2, y2, _ := planckianLocus(temp)
|
||||
factor := float64(4000-temp) / 1500.0
|
||||
sineFactor := (math.Cos(math.Pi*factor) + 1.0) / 2.0
|
||||
wp.x = x1*sineFactor + x2*(1.0-sineFactor)
|
||||
wp.y = y1*sineFactor + y2*(1.0-sineFactor)
|
||||
default:
|
||||
t := temp
|
||||
if t < 1667 {
|
||||
t = 1667
|
||||
}
|
||||
x, y, _ := planckianLocus(t)
|
||||
wp.x = x
|
||||
wp.y = y
|
||||
}
|
||||
|
||||
wp.z = 1.0 - wp.x - wp.y
|
||||
|
||||
wpRGB := xyzToSRGB(wp)
|
||||
normalizeRGB(&wpRGB)
|
||||
return wpRGB
|
||||
}
|
||||
|
||||
func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
|
||||
ramp := GammaRamp{
|
||||
Red: make([]uint16, size),
|
||||
@@ -19,16 +137,13 @@ func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
|
||||
Blue: make([]uint16, size),
|
||||
}
|
||||
|
||||
wp := calcWhitepoint(temp)
|
||||
|
||||
for i := uint32(0); i < size; i++ {
|
||||
val := float64(i) / float64(size-1)
|
||||
|
||||
valGamma := math.Pow(val, 1.0/gamma)
|
||||
|
||||
r, g, b := temperatureToRGB(temp)
|
||||
|
||||
ramp.Red[i] = uint16(utils.Clamp(valGamma*r*65535.0, 0, 65535))
|
||||
ramp.Green[i] = uint16(utils.Clamp(valGamma*g*65535.0, 0, 65535))
|
||||
ramp.Blue[i] = uint16(utils.Clamp(valGamma*b*65535.0, 0, 65535))
|
||||
ramp.Red[i] = uint16(clamp01(math.Pow(val*wp.r, 1.0/gamma)) * 65535.0)
|
||||
ramp.Green[i] = uint16(clamp01(math.Pow(val*wp.g, 1.0/gamma)) * 65535.0)
|
||||
ramp.Blue[i] = uint16(clamp01(math.Pow(val*wp.b, 1.0/gamma)) * 65535.0)
|
||||
}
|
||||
|
||||
return ramp
|
||||
@@ -50,39 +165,3 @@ func GenerateIdentityRamp(size uint32) GammaRamp {
|
||||
|
||||
return ramp
|
||||
}
|
||||
|
||||
func temperatureToRGB(temp int) (float64, float64, float64) {
|
||||
tempK := float64(temp) / 100.0
|
||||
|
||||
var r, g, b float64
|
||||
|
||||
if tempK <= 66 {
|
||||
r = 1.0
|
||||
} else {
|
||||
r = tempK - 60
|
||||
r = 329.698727446 * math.Pow(r, -0.1332047592)
|
||||
r = utils.Clamp(r, 0, 255) / 255.0
|
||||
}
|
||||
|
||||
if tempK <= 66 {
|
||||
g = tempK
|
||||
g = 99.4708025861*math.Log(g) - 161.1195681661
|
||||
g = utils.Clamp(g, 0, 255) / 255.0
|
||||
} else {
|
||||
g = tempK - 60
|
||||
g = 288.1221695283 * math.Pow(g, -0.0755148492)
|
||||
g = utils.Clamp(g, 0, 255) / 255.0
|
||||
}
|
||||
|
||||
if tempK >= 66 {
|
||||
b = 1.0
|
||||
} else if tempK <= 19 {
|
||||
b = 0.0
|
||||
} else {
|
||||
b = tempK - 10
|
||||
b = 138.5177312231*math.Log(b) - 305.0447927307
|
||||
b = utils.Clamp(b, 0, 255) / 255.0
|
||||
}
|
||||
|
||||
return r, g, b
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestGenerateGammaRamp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemperatureToRGB(t *testing.T) {
|
||||
func TestCalcWhitepoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
temp int
|
||||
@@ -67,32 +67,32 @@ func TestTemperatureToRGB(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r, g, b := temperatureToRGB(tt.temp)
|
||||
wp := calcWhitepoint(tt.temp)
|
||||
|
||||
if r < 0 || r > 1 {
|
||||
t.Errorf("red out of range: %f", r)
|
||||
if wp.r < 0 || wp.r > 1 {
|
||||
t.Errorf("red out of range: %f", wp.r)
|
||||
}
|
||||
if g < 0 || g > 1 {
|
||||
t.Errorf("green out of range: %f", g)
|
||||
if wp.g < 0 || wp.g > 1 {
|
||||
t.Errorf("green out of range: %f", wp.g)
|
||||
}
|
||||
if b < 0 || b > 1 {
|
||||
t.Errorf("blue out of range: %f", b)
|
||||
if wp.b < 0 || wp.b > 1 {
|
||||
t.Errorf("blue out of range: %f", wp.b)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemperatureProgression(t *testing.T) {
|
||||
func TestWhitepointProgression(t *testing.T) {
|
||||
temps := []int{3000, 4000, 5000, 6000, 6500}
|
||||
|
||||
var prevBlue float64
|
||||
for i, temp := range temps {
|
||||
_, _, b := temperatureToRGB(temp)
|
||||
if i > 0 && b < prevBlue {
|
||||
wp := calcWhitepoint(temp)
|
||||
if i > 0 && wp.b < prevBlue {
|
||||
t.Errorf("blue should increase with temperature, %d->%d: %f->%f",
|
||||
temps[i-1], temp, prevBlue, b)
|
||||
temps[i-1], temp, prevBlue, wp.b)
|
||||
}
|
||||
prevBlue = b
|
||||
prevBlue = wp.b
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,81 +6,117 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
degToRad = math.Pi / 180.0
|
||||
radToDeg = 180.0 / math.Pi
|
||||
solarNoon = 12.0
|
||||
sunriseAngle = -0.833
|
||||
degToRad = math.Pi / 180.0
|
||||
radToDeg = 180.0 / math.Pi
|
||||
)
|
||||
|
||||
func CalculateSunTimes(lat, lon float64, date time.Time) SunTimes {
|
||||
utcDate := date.UTC()
|
||||
year, month, day := utcDate.Date()
|
||||
loc := date.Location()
|
||||
type SunCondition int
|
||||
|
||||
dayOfYear := utcDate.YearDay()
|
||||
const (
|
||||
SunNormal SunCondition = iota
|
||||
SunMidnightSun
|
||||
SunPolarNight
|
||||
)
|
||||
|
||||
gamma := 2 * math.Pi / 365 * float64(dayOfYear-1)
|
||||
type SunTimes struct {
|
||||
Dawn time.Time
|
||||
Sunrise time.Time
|
||||
Sunset time.Time
|
||||
Night time.Time
|
||||
}
|
||||
|
||||
eqTime := 229.18 * (0.000075 +
|
||||
0.001868*math.Cos(gamma) -
|
||||
0.032077*math.Sin(gamma) -
|
||||
0.014615*math.Cos(2*gamma) -
|
||||
0.040849*math.Sin(2*gamma))
|
||||
func daysInYear(year int) int {
|
||||
if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
|
||||
return 366
|
||||
}
|
||||
return 365
|
||||
}
|
||||
|
||||
decl := 0.006918 -
|
||||
0.399912*math.Cos(gamma) +
|
||||
0.070257*math.Sin(gamma) -
|
||||
0.006758*math.Cos(2*gamma) +
|
||||
0.000907*math.Sin(2*gamma) -
|
||||
0.002697*math.Cos(3*gamma) +
|
||||
0.00148*math.Sin(3*gamma)
|
||||
func dateOrbitAngle(t time.Time) float64 {
|
||||
return 2 * math.Pi / float64(daysInYear(t.Year())) * float64(t.YearDay()-1)
|
||||
}
|
||||
|
||||
func equationOfTime(orbitAngle float64) float64 {
|
||||
return 4 * (0.000075 +
|
||||
0.001868*math.Cos(orbitAngle) -
|
||||
0.032077*math.Sin(orbitAngle) -
|
||||
0.014615*math.Cos(2*orbitAngle) -
|
||||
0.040849*math.Sin(2*orbitAngle))
|
||||
}
|
||||
|
||||
func sunDeclination(orbitAngle float64) float64 {
|
||||
return 0.006918 -
|
||||
0.399912*math.Cos(orbitAngle) +
|
||||
0.070257*math.Sin(orbitAngle) -
|
||||
0.006758*math.Cos(2*orbitAngle) +
|
||||
0.000907*math.Sin(2*orbitAngle) -
|
||||
0.002697*math.Cos(3*orbitAngle) +
|
||||
0.00148*math.Sin(3*orbitAngle)
|
||||
}
|
||||
|
||||
func sunHourAngle(latRad, declination, targetSunRad float64) float64 {
|
||||
return math.Acos(math.Cos(targetSunRad)/
|
||||
math.Cos(latRad)*math.Cos(declination) -
|
||||
math.Tan(latRad)*math.Tan(declination))
|
||||
}
|
||||
|
||||
func hourAngleToSeconds(hourAngle, eqtime float64) float64 {
|
||||
return radToDeg * (4.0*math.Pi - 4*hourAngle - eqtime) * 60
|
||||
}
|
||||
|
||||
func sunCondition(latRad, declination float64) SunCondition {
|
||||
signLat := latRad >= 0
|
||||
signDecl := declination >= 0
|
||||
if signLat == signDecl {
|
||||
return SunMidnightSun
|
||||
}
|
||||
return SunPolarNight
|
||||
}
|
||||
|
||||
func CalculateSunTimesWithTwilight(lat, lon float64, date time.Time, elevTwilight, elevDaylight float64) (SunTimes, SunCondition) {
|
||||
latRad := lat * degToRad
|
||||
elevTwilightRad := (90.833 - elevTwilight) * degToRad
|
||||
elevDaylightRad := (90.833 - elevDaylight) * degToRad
|
||||
|
||||
cosHourAngle := (math.Sin(sunriseAngle*degToRad) -
|
||||
math.Sin(latRad)*math.Sin(decl)) /
|
||||
(math.Cos(latRad) * math.Cos(decl))
|
||||
utc := date.UTC()
|
||||
orbitAngle := dateOrbitAngle(utc)
|
||||
decl := sunDeclination(orbitAngle)
|
||||
eqtime := equationOfTime(orbitAngle)
|
||||
|
||||
if cosHourAngle > 1 {
|
||||
return SunTimes{
|
||||
Sunrise: time.Date(year, month, day, 0, 0, 0, 0, time.UTC).In(loc),
|
||||
Sunset: time.Date(year, month, day, 0, 0, 0, 0, time.UTC).In(loc),
|
||||
}
|
||||
}
|
||||
if cosHourAngle < -1 {
|
||||
return SunTimes{
|
||||
Sunrise: time.Date(year, month, day, 0, 0, 0, 0, time.UTC).In(loc),
|
||||
Sunset: time.Date(year, month, day, 23, 59, 59, 0, time.UTC).In(loc),
|
||||
}
|
||||
haTwilight := sunHourAngle(latRad, decl, elevTwilightRad)
|
||||
haDaylight := sunHourAngle(latRad, decl, elevDaylightRad)
|
||||
|
||||
if math.IsNaN(haTwilight) || math.IsNaN(haDaylight) {
|
||||
cond := sunCondition(latRad, decl)
|
||||
return SunTimes{}, cond
|
||||
}
|
||||
|
||||
hourAngle := math.Acos(cosHourAngle) * radToDeg
|
||||
dayStart := time.Date(utc.Year(), utc.Month(), utc.Day(), 0, 0, 0, 0, time.UTC)
|
||||
lonOffset := time.Duration(-lon*4) * time.Minute
|
||||
|
||||
sunriseTime := solarNoon - hourAngle/15.0 - lon/15.0 - eqTime/60.0
|
||||
sunsetTime := solarNoon + hourAngle/15.0 - lon/15.0 - eqTime/60.0
|
||||
|
||||
sunrise := timeOfDayToTime(sunriseTime, year, month, day, time.UTC).In(loc)
|
||||
sunset := timeOfDayToTime(sunsetTime, year, month, day, time.UTC).In(loc)
|
||||
dawnSecs := hourAngleToSeconds(math.Abs(haTwilight), eqtime)
|
||||
sunriseSecs := hourAngleToSeconds(math.Abs(haDaylight), eqtime)
|
||||
sunsetSecs := hourAngleToSeconds(-math.Abs(haDaylight), eqtime)
|
||||
nightSecs := hourAngleToSeconds(-math.Abs(haTwilight), eqtime)
|
||||
|
||||
return SunTimes{
|
||||
Sunrise: sunrise,
|
||||
Sunset: sunset,
|
||||
}
|
||||
Dawn: dayStart.Add(time.Duration(dawnSecs)*time.Second + lonOffset).In(date.Location()),
|
||||
Sunrise: dayStart.Add(time.Duration(sunriseSecs)*time.Second + lonOffset).In(date.Location()),
|
||||
Sunset: dayStart.Add(time.Duration(sunsetSecs)*time.Second + lonOffset).In(date.Location()),
|
||||
Night: dayStart.Add(time.Duration(nightSecs)*time.Second + lonOffset).In(date.Location()),
|
||||
}, SunNormal
|
||||
}
|
||||
|
||||
func timeOfDayToTime(hours float64, year int, month time.Month, day int, loc *time.Location) time.Time {
|
||||
h := int(hours)
|
||||
m := int((hours - float64(h)) * 60)
|
||||
s := int(((hours-float64(h))*60 - float64(m)) * 60)
|
||||
|
||||
if h < 0 {
|
||||
h += 24
|
||||
day--
|
||||
func CalculateSunTimes(lat, lon float64, date time.Time) SunTimes {
|
||||
times, cond := CalculateSunTimesWithTwilight(lat, lon, date, -6.0, 3.0)
|
||||
switch cond {
|
||||
case SunMidnightSun:
|
||||
dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
dayEnd := dayStart.Add(24*time.Hour - time.Second)
|
||||
return SunTimes{Dawn: dayStart, Sunrise: dayStart, Sunset: dayEnd, Night: dayEnd}
|
||||
case SunPolarNight:
|
||||
dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
return SunTimes{Dawn: dayStart, Sunrise: dayStart, Sunset: dayStart, Night: dayStart}
|
||||
}
|
||||
if h >= 24 {
|
||||
h -= 24
|
||||
day++
|
||||
}
|
||||
|
||||
return time.Date(year, month, day, h, m, s, 0, loc)
|
||||
return times
|
||||
}
|
||||
|
||||
@@ -340,38 +340,47 @@ func TestCalculateNextTransition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeOfDayToTime(t *testing.T) {
|
||||
func TestSunTimesWithTwilight(t *testing.T) {
|
||||
lat := 40.7128
|
||||
lon := -74.0060
|
||||
date := time.Date(2024, 6, 21, 12, 0, 0, 0, time.Local)
|
||||
|
||||
times, cond := CalculateSunTimesWithTwilight(lat, lon, date, -6.0, 3.0)
|
||||
|
||||
if cond != SunNormal {
|
||||
t.Errorf("expected SunNormal, got %v", cond)
|
||||
}
|
||||
if !times.Dawn.Before(times.Sunrise) {
|
||||
t.Error("dawn should be before sunrise")
|
||||
}
|
||||
if !times.Sunrise.Before(times.Sunset) {
|
||||
t.Error("sunrise should be before sunset")
|
||||
}
|
||||
if !times.Sunset.Before(times.Night) {
|
||||
t.Error("sunset should be before night")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSunConditions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hours float64
|
||||
expected time.Time
|
||||
lat float64
|
||||
date time.Time
|
||||
expected SunCondition
|
||||
}{
|
||||
{
|
||||
name: "noon",
|
||||
hours: 12.0,
|
||||
expected: time.Date(2024, 6, 21, 12, 0, 0, 0, time.Local),
|
||||
},
|
||||
{
|
||||
name: "half_past",
|
||||
hours: 12.5,
|
||||
expected: time.Date(2024, 6, 21, 12, 30, 0, 0, time.Local),
|
||||
},
|
||||
{
|
||||
name: "early_morning",
|
||||
hours: 6.25,
|
||||
expected: time.Date(2024, 6, 21, 6, 15, 0, 0, time.Local),
|
||||
name: "normal_conditions",
|
||||
lat: 40.0,
|
||||
date: time.Date(2024, 6, 21, 12, 0, 0, 0, time.UTC),
|
||||
expected: SunNormal,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := timeOfDayToTime(tt.hours, 2024, 6, 21, time.Local)
|
||||
|
||||
if result.Hour() != tt.expected.Hour() {
|
||||
t.Errorf("hour = %d, want %d", result.Hour(), tt.expected.Hour())
|
||||
}
|
||||
if result.Minute() != tt.expected.Minute() {
|
||||
t.Errorf("minute = %d, want %d", result.Minute(), tt.expected.Minute())
|
||||
_, cond := CalculateSunTimesWithTwilight(tt.lat, 0, tt.date, -6.0, 3.0)
|
||||
if cond != tt.expected {
|
||||
t.Errorf("expected condition %v, got %v", tt.expected, cond)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,18 +11,28 @@ import (
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
type GammaState int
|
||||
|
||||
const (
|
||||
StateNormal GammaState = iota
|
||||
StateTransition
|
||||
StateStatic
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Outputs []string
|
||||
LowTemp int
|
||||
HighTemp int
|
||||
Latitude *float64
|
||||
Longitude *float64
|
||||
UseIPLocation bool
|
||||
ManualSunrise *time.Time
|
||||
ManualSunset *time.Time
|
||||
ManualDuration *time.Duration
|
||||
Gamma float64
|
||||
Enabled bool
|
||||
Outputs []string
|
||||
LowTemp int
|
||||
HighTemp int
|
||||
Latitude *float64
|
||||
Longitude *float64
|
||||
UseIPLocation bool
|
||||
ManualSunrise *time.Time
|
||||
ManualSunset *time.Time
|
||||
ManualDuration *time.Duration
|
||||
Gamma float64
|
||||
Enabled bool
|
||||
ElevationTwilight float64
|
||||
ElevationDaylight float64
|
||||
}
|
||||
|
||||
type State struct {
|
||||
@@ -31,13 +41,24 @@ type State struct {
|
||||
NextTransition time.Time `json:"nextTransition"`
|
||||
SunriseTime time.Time `json:"sunriseTime"`
|
||||
SunsetTime time.Time `json:"sunsetTime"`
|
||||
DawnTime time.Time `json:"dawnTime"`
|
||||
NightTime time.Time `json:"nightTime"`
|
||||
IsDay bool `json:"isDay"`
|
||||
SunPosition float64 `json:"sunPosition"`
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
fn func()
|
||||
}
|
||||
|
||||
type sunSchedule struct {
|
||||
times SunTimes
|
||||
condition SunCondition
|
||||
dawnStepTime time.Duration
|
||||
nightStepTime time.Duration
|
||||
calcDay time.Time
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
config Config
|
||||
configMutex sync.RWMutex
|
||||
@@ -60,10 +81,9 @@ type Manager struct {
|
||||
updateTrigger chan struct{}
|
||||
wg sync.WaitGroup
|
||||
|
||||
currentTemp int
|
||||
targetTemp int
|
||||
transitionMutex sync.RWMutex
|
||||
transitionChan chan int
|
||||
schedule sunSchedule
|
||||
scheduleMutex sync.RWMutex
|
||||
gammaState GammaState
|
||||
|
||||
cachedIPLat *float64
|
||||
cachedIPLon *float64
|
||||
@@ -80,7 +100,6 @@ type Manager struct {
|
||||
|
||||
type outputState struct {
|
||||
id uint32
|
||||
name string
|
||||
registryName uint32
|
||||
output *wlclient.Output
|
||||
gammaControl any
|
||||
@@ -91,18 +110,15 @@ type outputState struct {
|
||||
lastFailTime time.Time
|
||||
}
|
||||
|
||||
type SunTimes struct {
|
||||
Sunrise time.Time
|
||||
Sunset time.Time
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Outputs: []string{},
|
||||
LowTemp: 4000,
|
||||
HighTemp: 6500,
|
||||
Gamma: 1.0,
|
||||
Enabled: false,
|
||||
Outputs: []string{},
|
||||
LowTemp: 4000,
|
||||
HighTemp: 6500,
|
||||
Gamma: 1.0,
|
||||
Enabled: false,
|
||||
ElevationTwilight: -6.0,
|
||||
ElevationDaylight: 3.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +156,7 @@ func (m *Manager) GetState() State {
|
||||
if m.state == nil {
|
||||
return State{}
|
||||
}
|
||||
stateCopy := *m.state
|
||||
return stateCopy
|
||||
return *m.state
|
||||
}
|
||||
|
||||
func (m *Manager) Subscribe(id string) chan State {
|
||||
@@ -185,5 +200,8 @@ func stateChanged(old, new *State) bool {
|
||||
if old.Config.Enabled != new.Config.Enabled {
|
||||
return true
|
||||
}
|
||||
if old.SunPosition != new.SunPosition {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -96,6 +96,8 @@ DankModal {
|
||||
|
||||
for (let i = 0; i < binds.length; i++) {
|
||||
const bind = binds[i];
|
||||
if (bind.hideOnOverlay)
|
||||
continue;
|
||||
if (bind.subcat) {
|
||||
hasSubcats = true;
|
||||
if (!subcats[bind.subcat])
|
||||
@@ -108,6 +110,9 @@ DankModal {
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(subcats).length === 0)
|
||||
continue;
|
||||
|
||||
processed[cat] = {
|
||||
hasSubcats: hasSubcats,
|
||||
subcats: subcats,
|
||||
|
||||
Reference in New Issue
Block a user