mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-31 08:52:49 -05:00
gamma: switch to wlsunset-style transitions
This commit is contained in:
@@ -22,6 +22,8 @@ linters:
|
|||||||
- (*os.Process).Signal
|
- (*os.Process).Signal
|
||||||
- (*os.Process).Kill
|
- (*os.Process).Kill
|
||||||
- syscall.Kill
|
- syscall.Kill
|
||||||
|
# Seek on memfd (reset position before passing fd)
|
||||||
|
- syscall.Seek
|
||||||
# DBus cleanup
|
# DBus cleanup
|
||||||
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
||||||
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, co
|
|||||||
Action: rawAction,
|
Action: rawAction,
|
||||||
Subcategory: subcategory,
|
Subcategory: subcategory,
|
||||||
Source: source,
|
Source: source,
|
||||||
|
HideOnOverlay: kb.HideOnOverlay,
|
||||||
}
|
}
|
||||||
|
|
||||||
if source == "dms" && conflicts != nil {
|
if source == "dms" && conflicts != nil {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type NiriKeyBinding struct {
|
|||||||
Action string
|
Action string
|
||||||
Args []string
|
Args []string
|
||||||
Description string
|
Description string
|
||||||
|
HideOnOverlay bool
|
||||||
Source string
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,11 +274,17 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
|
|||||||
}
|
}
|
||||||
|
|
||||||
var description string
|
var description string
|
||||||
|
var hideOnOverlay bool
|
||||||
if node.Properties != nil {
|
if node.Properties != nil {
|
||||||
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
||||||
|
switch val.ValueString() {
|
||||||
|
case "null", "":
|
||||||
|
hideOnOverlay = true
|
||||||
|
default:
|
||||||
description = val.ValueString()
|
description = val.ValueString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &NiriKeyBinding{
|
return &NiriKeyBinding{
|
||||||
Mods: mods,
|
Mods: mods,
|
||||||
@@ -285,6 +292,7 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
|
|||||||
Action: action,
|
Action: action,
|
||||||
Args: args,
|
Args: args,
|
||||||
Description: description,
|
Description: description,
|
||||||
|
HideOnOverlay: hideOnOverlay,
|
||||||
Source: p.currentSource,
|
Source: p.currentSource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ type Keybind struct {
|
|||||||
Action string `json:"action,omitempty"`
|
Action string `json:"action,omitempty"`
|
||||||
Subcategory string `json:"subcat,omitempty"`
|
Subcategory string `json:"subcat,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
|
HideOnOverlay bool `json:"hideOnOverlay,omitempty"`
|
||||||
Conflict *Keybind `json:"conflict,omitempty"`
|
Conflict *Keybind `json:"conflict,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,9 +238,17 @@ func (i *ZwlrOutputManagerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy != nil {
|
if proxy == nil {
|
||||||
e.Head = proxy.(*ZwlrOutputHeadV1)
|
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 {
|
} else {
|
||||||
|
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||||
|
// Replace it with the correct type
|
||||||
head := &ZwlrOutputHeadV1{}
|
head := &ZwlrOutputHeadV1{}
|
||||||
head.SetContext(i.Context())
|
head.SetContext(i.Context())
|
||||||
head.SetID(objectID)
|
head.SetID(objectID)
|
||||||
@@ -715,9 +723,17 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
l := 0
|
l := 0
|
||||||
objectID := client.Uint32(data[l : l+4])
|
objectID := client.Uint32(data[l : l+4])
|
||||||
proxy := i.Context().GetProxy(objectID)
|
proxy := i.Context().GetProxy(objectID)
|
||||||
if proxy != nil {
|
if proxy == nil {
|
||||||
e.Mode = proxy.(*ZwlrOutputModeV1)
|
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 {
|
} else {
|
||||||
|
// Stale proxy of wrong type (can happen after suspend/resume)
|
||||||
|
// Replace it with the correct type
|
||||||
mode := &ZwlrOutputModeV1{}
|
mode := &ZwlrOutputModeV1{}
|
||||||
mode.SetContext(i.Context())
|
mode.SetContext(i.Context())
|
||||||
mode.SetID(objectID)
|
mode.SetID(objectID)
|
||||||
@@ -743,7 +759,26 @@ func (i *ZwlrOutputHeadV1) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
}
|
}
|
||||||
var e ZwlrOutputHeadV1CurrentModeEvent
|
var e ZwlrOutputHeadV1CurrentModeEvent
|
||||||
l := 0
|
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
|
l += 4
|
||||||
|
|
||||||
i.currentModeHandler(e)
|
i.currentModeHandler(e)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package wayland
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GammaRamp struct {
|
type GammaRamp struct {
|
||||||
@@ -12,6 +10,126 @@ type GammaRamp struct {
|
|||||||
Blue []uint16
|
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 {
|
func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
|
||||||
ramp := GammaRamp{
|
ramp := GammaRamp{
|
||||||
Red: make([]uint16, size),
|
Red: make([]uint16, size),
|
||||||
@@ -19,16 +137,13 @@ func GenerateGammaRamp(size uint32, temp int, gamma float64) GammaRamp {
|
|||||||
Blue: make([]uint16, size),
|
Blue: make([]uint16, size),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wp := calcWhitepoint(temp)
|
||||||
|
|
||||||
for i := uint32(0); i < size; i++ {
|
for i := uint32(0); i < size; i++ {
|
||||||
val := float64(i) / float64(size-1)
|
val := float64(i) / float64(size-1)
|
||||||
|
ramp.Red[i] = uint16(clamp01(math.Pow(val*wp.r, 1.0/gamma)) * 65535.0)
|
||||||
valGamma := math.Pow(val, 1.0/gamma)
|
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)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ramp
|
return ramp
|
||||||
@@ -50,39 +165,3 @@ func GenerateIdentityRamp(size uint32) GammaRamp {
|
|||||||
|
|
||||||
return ramp
|
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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
temp int
|
temp int
|
||||||
@@ -67,32 +67,32 @@ func TestTemperatureToRGB(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
r, g, b := temperatureToRGB(tt.temp)
|
wp := calcWhitepoint(tt.temp)
|
||||||
|
|
||||||
if r < 0 || r > 1 {
|
if wp.r < 0 || wp.r > 1 {
|
||||||
t.Errorf("red out of range: %f", r)
|
t.Errorf("red out of range: %f", wp.r)
|
||||||
}
|
}
|
||||||
if g < 0 || g > 1 {
|
if wp.g < 0 || wp.g > 1 {
|
||||||
t.Errorf("green out of range: %f", g)
|
t.Errorf("green out of range: %f", wp.g)
|
||||||
}
|
}
|
||||||
if b < 0 || b > 1 {
|
if wp.b < 0 || wp.b > 1 {
|
||||||
t.Errorf("blue out of range: %f", b)
|
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}
|
temps := []int{3000, 4000, 5000, 6000, 6500}
|
||||||
|
|
||||||
var prevBlue float64
|
var prevBlue float64
|
||||||
for i, temp := range temps {
|
for i, temp := range temps {
|
||||||
_, _, b := temperatureToRGB(temp)
|
wp := calcWhitepoint(temp)
|
||||||
if i > 0 && b < prevBlue {
|
if i > 0 && wp.b < prevBlue {
|
||||||
t.Errorf("blue should increase with temperature, %d->%d: %f->%f",
|
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
@@ -8,79 +8,115 @@ import (
|
|||||||
const (
|
const (
|
||||||
degToRad = math.Pi / 180.0
|
degToRad = math.Pi / 180.0
|
||||||
radToDeg = 180.0 / math.Pi
|
radToDeg = 180.0 / math.Pi
|
||||||
solarNoon = 12.0
|
|
||||||
sunriseAngle = -0.833
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CalculateSunTimes(lat, lon float64, date time.Time) SunTimes {
|
type SunCondition int
|
||||||
utcDate := date.UTC()
|
|
||||||
year, month, day := utcDate.Date()
|
|
||||||
loc := date.Location()
|
|
||||||
|
|
||||||
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 +
|
func daysInYear(year int) int {
|
||||||
0.001868*math.Cos(gamma) -
|
if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
|
||||||
0.032077*math.Sin(gamma) -
|
return 366
|
||||||
0.014615*math.Cos(2*gamma) -
|
}
|
||||||
0.040849*math.Sin(2*gamma))
|
return 365
|
||||||
|
}
|
||||||
|
|
||||||
decl := 0.006918 -
|
func dateOrbitAngle(t time.Time) float64 {
|
||||||
0.399912*math.Cos(gamma) +
|
return 2 * math.Pi / float64(daysInYear(t.Year())) * float64(t.YearDay()-1)
|
||||||
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 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
|
latRad := lat * degToRad
|
||||||
|
elevTwilightRad := (90.833 - elevTwilight) * degToRad
|
||||||
|
elevDaylightRad := (90.833 - elevDaylight) * degToRad
|
||||||
|
|
||||||
cosHourAngle := (math.Sin(sunriseAngle*degToRad) -
|
utc := date.UTC()
|
||||||
math.Sin(latRad)*math.Sin(decl)) /
|
orbitAngle := dateOrbitAngle(utc)
|
||||||
(math.Cos(latRad) * math.Cos(decl))
|
decl := sunDeclination(orbitAngle)
|
||||||
|
eqtime := equationOfTime(orbitAngle)
|
||||||
|
|
||||||
if cosHourAngle > 1 {
|
haTwilight := sunHourAngle(latRad, decl, elevTwilightRad)
|
||||||
return SunTimes{
|
haDaylight := sunHourAngle(latRad, decl, elevDaylightRad)
|
||||||
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 math.IsNaN(haTwilight) || math.IsNaN(haDaylight) {
|
||||||
}
|
cond := sunCondition(latRad, decl)
|
||||||
}
|
return SunTimes{}, cond
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
dawnSecs := hourAngleToSeconds(math.Abs(haTwilight), eqtime)
|
||||||
sunsetTime := solarNoon + hourAngle/15.0 - lon/15.0 - eqTime/60.0
|
sunriseSecs := hourAngleToSeconds(math.Abs(haDaylight), eqtime)
|
||||||
|
sunsetSecs := hourAngleToSeconds(-math.Abs(haDaylight), eqtime)
|
||||||
sunrise := timeOfDayToTime(sunriseTime, year, month, day, time.UTC).In(loc)
|
nightSecs := hourAngleToSeconds(-math.Abs(haTwilight), eqtime)
|
||||||
sunset := timeOfDayToTime(sunsetTime, year, month, day, time.UTC).In(loc)
|
|
||||||
|
|
||||||
return SunTimes{
|
return SunTimes{
|
||||||
Sunrise: sunrise,
|
Dawn: dayStart.Add(time.Duration(dawnSecs)*time.Second + lonOffset).In(date.Location()),
|
||||||
Sunset: sunset,
|
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 {
|
func CalculateSunTimes(lat, lon float64, date time.Time) SunTimes {
|
||||||
h := int(hours)
|
times, cond := CalculateSunTimesWithTwilight(lat, lon, date, -6.0, 3.0)
|
||||||
m := int((hours - float64(h)) * 60)
|
switch cond {
|
||||||
s := int(((hours-float64(h))*60 - float64(m)) * 60)
|
case SunMidnightSun:
|
||||||
|
dayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||||
if h < 0 {
|
dayEnd := dayStart.Add(24*time.Hour - time.Second)
|
||||||
h += 24
|
return SunTimes{Dawn: dayStart, Sunrise: dayStart, Sunset: dayEnd, Night: dayEnd}
|
||||||
day--
|
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 {
|
return times
|
||||||
h -= 24
|
|
||||||
day++
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Date(year, month, day, h, m, s, 0, loc)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
hours float64
|
lat float64
|
||||||
expected time.Time
|
date time.Time
|
||||||
|
expected SunCondition
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "noon",
|
name: "normal_conditions",
|
||||||
hours: 12.0,
|
lat: 40.0,
|
||||||
expected: time.Date(2024, 6, 21, 12, 0, 0, 0, time.Local),
|
date: time.Date(2024, 6, 21, 12, 0, 0, 0, time.UTC),
|
||||||
},
|
expected: SunNormal,
|
||||||
{
|
|
||||||
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),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := timeOfDayToTime(tt.hours, 2024, 6, 21, time.Local)
|
_, cond := CalculateSunTimesWithTwilight(tt.lat, 0, tt.date, -6.0, 3.0)
|
||||||
|
if cond != tt.expected {
|
||||||
if result.Hour() != tt.expected.Hour() {
|
t.Errorf("expected condition %v, got %v", tt.expected, cond)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ import (
|
|||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GammaState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateNormal GammaState = iota
|
||||||
|
StateTransition
|
||||||
|
StateStatic
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Outputs []string
|
Outputs []string
|
||||||
LowTemp int
|
LowTemp int
|
||||||
@@ -23,6 +31,8 @@ type Config struct {
|
|||||||
ManualDuration *time.Duration
|
ManualDuration *time.Duration
|
||||||
Gamma float64
|
Gamma float64
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
ElevationTwilight float64
|
||||||
|
ElevationDaylight float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
@@ -31,13 +41,24 @@ type State struct {
|
|||||||
NextTransition time.Time `json:"nextTransition"`
|
NextTransition time.Time `json:"nextTransition"`
|
||||||
SunriseTime time.Time `json:"sunriseTime"`
|
SunriseTime time.Time `json:"sunriseTime"`
|
||||||
SunsetTime time.Time `json:"sunsetTime"`
|
SunsetTime time.Time `json:"sunsetTime"`
|
||||||
|
DawnTime time.Time `json:"dawnTime"`
|
||||||
|
NightTime time.Time `json:"nightTime"`
|
||||||
IsDay bool `json:"isDay"`
|
IsDay bool `json:"isDay"`
|
||||||
|
SunPosition float64 `json:"sunPosition"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cmd struct {
|
type cmd struct {
|
||||||
fn func()
|
fn func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sunSchedule struct {
|
||||||
|
times SunTimes
|
||||||
|
condition SunCondition
|
||||||
|
dawnStepTime time.Duration
|
||||||
|
nightStepTime time.Duration
|
||||||
|
calcDay time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
config Config
|
config Config
|
||||||
configMutex sync.RWMutex
|
configMutex sync.RWMutex
|
||||||
@@ -60,10 +81,9 @@ type Manager struct {
|
|||||||
updateTrigger chan struct{}
|
updateTrigger chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
currentTemp int
|
schedule sunSchedule
|
||||||
targetTemp int
|
scheduleMutex sync.RWMutex
|
||||||
transitionMutex sync.RWMutex
|
gammaState GammaState
|
||||||
transitionChan chan int
|
|
||||||
|
|
||||||
cachedIPLat *float64
|
cachedIPLat *float64
|
||||||
cachedIPLon *float64
|
cachedIPLon *float64
|
||||||
@@ -80,7 +100,6 @@ type Manager struct {
|
|||||||
|
|
||||||
type outputState struct {
|
type outputState struct {
|
||||||
id uint32
|
id uint32
|
||||||
name string
|
|
||||||
registryName uint32
|
registryName uint32
|
||||||
output *wlclient.Output
|
output *wlclient.Output
|
||||||
gammaControl any
|
gammaControl any
|
||||||
@@ -91,11 +110,6 @@ type outputState struct {
|
|||||||
lastFailTime time.Time
|
lastFailTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type SunTimes struct {
|
|
||||||
Sunrise time.Time
|
|
||||||
Sunset time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Outputs: []string{},
|
Outputs: []string{},
|
||||||
@@ -103,6 +117,8 @@ func DefaultConfig() Config {
|
|||||||
HighTemp: 6500,
|
HighTemp: 6500,
|
||||||
Gamma: 1.0,
|
Gamma: 1.0,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
ElevationTwilight: -6.0,
|
||||||
|
ElevationDaylight: 3.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +156,7 @@ func (m *Manager) GetState() State {
|
|||||||
if m.state == nil {
|
if m.state == nil {
|
||||||
return State{}
|
return State{}
|
||||||
}
|
}
|
||||||
stateCopy := *m.state
|
return *m.state
|
||||||
return stateCopy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan 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 {
|
if old.Config.Enabled != new.Config.Enabled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if old.SunPosition != new.SunPosition {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ DankModal {
|
|||||||
|
|
||||||
for (let i = 0; i < binds.length; i++) {
|
for (let i = 0; i < binds.length; i++) {
|
||||||
const bind = binds[i];
|
const bind = binds[i];
|
||||||
|
if (bind.hideOnOverlay)
|
||||||
|
continue;
|
||||||
if (bind.subcat) {
|
if (bind.subcat) {
|
||||||
hasSubcats = true;
|
hasSubcats = true;
|
||||||
if (!subcats[bind.subcat])
|
if (!subcats[bind.subcat])
|
||||||
@@ -108,6 +110,9 @@ DankModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(subcats).length === 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
processed[cat] = {
|
processed[cat] = {
|
||||||
hasSubcats: hasSubcats,
|
hasSubcats: hasSubcats,
|
||||||
subcats: subcats,
|
subcats: subcats,
|
||||||
|
|||||||
Reference in New Issue
Block a user