summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
committerNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
commita2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch)
tree1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland
parent34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff)
downloadRaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz
RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go167
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go120
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go50
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go182
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go1256
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go414
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go122
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go387
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go216
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go330
10 files changed, 0 insertions, 3244 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go
deleted file mode 100644
index 1fff03f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package wayland
-
-import (
- "math"
-)
-
-type GammaRamp struct {
- Red []uint16
- Green []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 {
- ramp := GammaRamp{
- Red: make([]uint16, size),
- Green: make([]uint16, size),
- Blue: make([]uint16, size),
- }
-
- wp := calcWhitepoint(temp)
-
- for i := uint32(0); i < size; i++ {
- val := float64(i) / float64(size-1)
- 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
-}
-
-func GenerateIdentityRamp(size uint32) GammaRamp {
- ramp := GammaRamp{
- Red: make([]uint16, size),
- Green: make([]uint16, size),
- Blue: make([]uint16, size),
- }
-
- for i := uint32(0); i < size; i++ {
- val := uint16((float64(i) / float64(size-1)) * 65535.0)
- ramp.Red[i] = val
- ramp.Green[i] = val
- ramp.Blue[i] = val
- }
-
- return ramp
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go
deleted file mode 100644
index 0bf8e71..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/gamma_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package wayland
-
-import (
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-func TestGenerateGammaRamp(t *testing.T) {
- tests := []struct {
- name string
- size uint32
- temp int
- gamma float64
- }{
- {"small_warm", 16, 6500, 1.0},
- {"small_cool", 16, 4000, 1.0},
- {"large_warm", 256, 6500, 1.0},
- {"large_cool", 256, 4000, 1.0},
- {"custom_gamma", 64, 5500, 1.2},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- ramp := GenerateGammaRamp(tt.size, tt.temp, tt.gamma)
-
- if len(ramp.Red) != int(tt.size) {
- t.Errorf("expected %d red values, got %d", tt.size, len(ramp.Red))
- }
- if len(ramp.Green) != int(tt.size) {
- t.Errorf("expected %d green values, got %d", tt.size, len(ramp.Green))
- }
- if len(ramp.Blue) != int(tt.size) {
- t.Errorf("expected %d blue values, got %d", tt.size, len(ramp.Blue))
- }
-
- if ramp.Red[0] != 0 || ramp.Green[0] != 0 || ramp.Blue[0] != 0 {
- t.Errorf("first values should be 0, got R:%d G:%d B:%d",
- ramp.Red[0], ramp.Green[0], ramp.Blue[0])
- }
-
- lastIdx := tt.size - 1
- if ramp.Red[lastIdx] == 0 || ramp.Green[lastIdx] == 0 || ramp.Blue[lastIdx] == 0 {
- t.Errorf("last values should be non-zero, got R:%d G:%d B:%d",
- ramp.Red[lastIdx], ramp.Green[lastIdx], ramp.Blue[lastIdx])
- }
-
- for i := uint32(1); i < tt.size; i++ {
- if ramp.Red[i] < ramp.Red[i-1] {
- t.Errorf("red ramp not monotonic at index %d", i)
- }
- }
- })
- }
-}
-
-func TestCalcWhitepoint(t *testing.T) {
- tests := []struct {
- name string
- temp int
- }{
- {"very_warm", 6500},
- {"neutral", 5500},
- {"cool", 4000},
- {"very_cool", 3000},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- wp := calcWhitepoint(tt.temp)
-
- if wp.r < 0 || wp.r > 1 {
- t.Errorf("red out of range: %f", wp.r)
- }
- if wp.g < 0 || wp.g > 1 {
- t.Errorf("green out of range: %f", wp.g)
- }
- if wp.b < 0 || wp.b > 1 {
- t.Errorf("blue out of range: %f", wp.b)
- }
- })
- }
-}
-
-func TestWhitepointProgression(t *testing.T) {
- temps := []int{3000, 4000, 5000, 6000, 6500}
-
- var prevBlue float64
- for i, temp := range temps {
- 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, wp.b)
- }
- prevBlue = wp.b
- }
-}
-
-func TestClamp(t *testing.T) {
- tests := []struct {
- val float64
- min float64
- max float64
- expected float64
- }{
- {5, 0, 10, 5},
- {-5, 0, 10, 0},
- {15, 0, 10, 10},
- {0, 0, 10, 0},
- {10, 0, 10, 10},
- }
-
- for _, tt := range tests {
- result := utils.Clamp(tt.val, tt.min, tt.max)
- if result != tt.expected {
- t.Errorf("clamp(%f, %f, %f) = %f, want %f",
- tt.val, tt.min, tt.max, result, tt.expected)
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go
deleted file mode 100644
index ba99b5f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/geolocation.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package wayland
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-type ipAPIResponse struct {
- Lat float64 `json:"lat"`
- Lon float64 `json:"lon"`
- City string `json:"city"`
-}
-
-func FetchIPLocation() (*float64, *float64, error) {
- client := &http.Client{
- Timeout: 10 * time.Second,
- }
-
- resp, err := client.Get("http://ip-api.com/json/")
- if err != nil {
- return nil, nil, fmt.Errorf("failed to fetch IP location: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, nil, fmt.Errorf("ip-api.com returned status %d", resp.StatusCode)
- }
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to read response: %w", err)
- }
-
- var data ipAPIResponse
- if err := json.Unmarshal(body, &data); err != nil {
- return nil, nil, fmt.Errorf("failed to parse response: %w", err)
- }
-
- if data.Lat == 0 && data.Lon == 0 {
- return nil, nil, fmt.Errorf("missing location data in response")
- }
-
- log.Infof("Fetched IP-based location: %s (%.4f, %.4f)", data.City, data.Lat, data.Lon)
- return &data.Lat, &data.Lon, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go
deleted file mode 100644
index 76b08b5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/handlers.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package wayland
-
-import (
- "encoding/json"
- "fmt"
- "net"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
-)
-
-func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
- if manager == nil {
- models.RespondError(conn, req.ID, "wayland manager not initialized")
- return
- }
-
- switch req.Method {
- case "wayland.gamma.getState":
- handleGetState(conn, req, manager)
- case "wayland.gamma.setTemperature":
- handleSetTemperature(conn, req, manager)
- case "wayland.gamma.setLocation":
- handleSetLocation(conn, req, manager)
- case "wayland.gamma.setManualTimes":
- handleSetManualTimes(conn, req, manager)
- case "wayland.gamma.setUseIPLocation":
- handleSetUseIPLocation(conn, req, manager)
- case "wayland.gamma.setGamma":
- handleSetGamma(conn, req, manager)
- case "wayland.gamma.setEnabled":
- handleSetEnabled(conn, req, manager)
- case "wayland.gamma.subscribe":
- handleSubscribe(conn, req, manager)
- default:
- models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
- }
-}
-
-func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
- models.Respond(conn, req.ID, manager.GetState())
-}
-
-func handleSetTemperature(conn net.Conn, req models.Request, manager *Manager) {
- var lowTemp, highTemp int
-
- if temp, ok := models.Get[float64](req, "temp"); ok {
- lowTemp = int(temp)
- highTemp = int(temp)
- } else {
- low, err := params.Float(req.Params, "low")
- if err != nil {
- models.RespondError(conn, req.ID, "missing temperature parameters (provide 'temp' or both 'low' and 'high')")
- return
- }
- high, err := params.Float(req.Params, "high")
- if err != nil {
- models.RespondError(conn, req.ID, "missing temperature parameters (provide 'temp' or both 'low' and 'high')")
- return
- }
- lowTemp = int(low)
- highTemp = int(high)
- }
-
- if err := manager.SetTemperature(lowTemp, highTemp); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "temperature set"})
-}
-
-func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
- lat, err := params.Float(req.Params, "latitude")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- lon, err := params.Float(req.Params, "longitude")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetLocation(lat, lon); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location set"})
-}
-
-func handleSetManualTimes(conn net.Conn, req models.Request, manager *Manager) {
- sunriseStr, sunriseOK := models.Get[string](req, "sunrise")
- sunsetStr, sunsetOK := models.Get[string](req, "sunset")
-
- if !sunriseOK || !sunsetOK || sunriseStr == "" || sunsetStr == "" {
- manager.ClearManualTimes()
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times cleared"})
- return
- }
-
- sunrise, err := time.Parse("15:04", sunriseStr)
- if err != nil {
- models.RespondError(conn, req.ID, "invalid sunrise format (use HH:MM)")
- return
- }
-
- sunset, err := time.Parse("15:04", sunsetStr)
- if err != nil {
- models.RespondError(conn, req.ID, "invalid sunset format (use HH:MM)")
- return
- }
-
- if err := manager.SetManualTimes(sunrise, sunset); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times set"})
-}
-
-func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) {
- use, err := params.Bool(req.Params, "use")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetUseIPLocation(use)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "IP location preference set"})
-}
-
-func handleSetGamma(conn net.Conn, req models.Request, manager *Manager) {
- gamma, err := params.Float(req.Params, "gamma")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- if err := manager.SetGamma(gamma); err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "gamma set"})
-}
-
-func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) {
- enabled, err := params.Bool(req.Params, "enabled")
- if err != nil {
- models.RespondError(conn, req.ID, err.Error())
- return
- }
-
- manager.SetEnabled(enabled)
- models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "enabled state set"})
-}
-
-func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
- clientID := fmt.Sprintf("client-%p", conn)
- stateChan := manager.Subscribe(clientID)
- defer manager.Unsubscribe(clientID)
-
- initialState := manager.GetState()
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- ID: req.ID,
- Result: &initialState,
- }); err != nil {
- return
- }
-
- for state := range stateChan {
- if err := json.NewEncoder(conn).Encode(models.Response[State]{
- Result: &state,
- }); err != nil {
- return
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go
deleted file mode 100644
index 3540e3d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager.go
+++ /dev/null
@@ -1,1256 +0,0 @@
-package wayland
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "os"
- "slices"
- "syscall"
- "time"
-
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
- "github.com/godbus/dbus/v5"
- "golang.org/x/sys/unix"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_gamma_control"
-)
-
-const animKelvinStep = 25
-
-func NewManager(display wlclient.WaylandDisplay, config Config) (*Manager, error) {
- if err := config.Validate(); err != nil {
- return nil, err
- }
-
- if config.ElevationTwilight == 0 {
- config.ElevationTwilight = -6.0
- }
- if config.ElevationDaylight == 0 {
- config.ElevationDaylight = 3.0
- }
-
- m := &Manager{
- config: config,
- display: display,
- ctx: display.Context(),
- cmdq: make(chan cmd, 128),
- stopChan: make(chan struct{}),
- updateTrigger: make(chan struct{}, 1),
- dirty: make(chan struct{}, 1),
- dbusSignal: make(chan *dbus.Signal, 16),
- }
-
- if err := m.setupRegistry(); err != nil {
- return nil, err
- }
-
- if err := m.setupDBusMonitor(); err != nil {
- log.Warnf("Failed to setup D-Bus monitoring: %v", err)
- }
-
- m.alive = true
- m.recalcSchedule(time.Now())
- m.updateStateFromSchedule()
-
- m.notifierWg.Add(1)
- go m.notifier()
-
- m.wg.Add(1)
- go m.schedulerLoop()
-
- if m.dbusConn != nil {
- m.wg.Add(1)
- go m.dbusMonitor()
- }
-
- m.wg.Add(1)
- go m.waylandActor()
-
- if config.Enabled {
- m.post(func() {
- log.Info("Gamma control enabled at startup")
- gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
- m.availOutputsMu.RLock()
- outs := slices.Clone(m.availableOutputs)
- m.availOutputsMu.RUnlock()
- if err := m.setupOutputControls(outs, gammaMgr); err != nil {
- log.Errorf("Failed to initialize gamma controls: %v", err)
- return
- }
- m.controlsInitialized = true
- })
- }
-
- return m, nil
-}
-
-func (m *Manager) post(fn func()) {
- select {
- case m.cmdq <- cmd{fn: fn}:
- default:
- log.Warn("Actor command queue full")
- }
-}
-
-func (m *Manager) waylandActor() {
- defer m.wg.Done()
- for {
- select {
- case <-m.stopChan:
- return
- case c := <-m.cmdq:
- c.fn()
- }
- }
-}
-
-func (m *Manager) anyOutputReady() bool {
- anyReady := false
- m.outputs.Range(func(_ uint32, out *outputState) bool {
- if out.rampSize > 0 && !out.failed {
- anyReady = true
- return false // stop iteration
- }
- return true
- })
- return anyReady
-}
-
-func (m *Manager) setupDBusMonitor() error {
- conn, err := dbus.ConnectSystemBus()
- if err != nil {
- return fmt.Errorf("system bus: %w", err)
- }
-
- matchRule := "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep',path='/org/freedesktop/login1'"
- if err := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule).Err; err != nil {
- conn.Close()
- return fmt.Errorf("add match: %w", err)
- }
-
- conn.Signal(m.dbusSignal)
- m.dbusConn = conn
- return nil
-}
-
-func (m *Manager) setupRegistry() error {
- registry, err := m.display.GetRegistry()
- if err != nil {
- return fmt.Errorf("get registry: %w", err)
- }
- m.registry = registry
-
- outputs := make([]*wlclient.Output, 0)
- outputNames := make(map[uint32]string)
- var gammaMgr *wlr_gamma_control.ZwlrGammaControlManagerV1
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- switch e.Interface {
- case wlr_gamma_control.ZwlrGammaControlManagerV1InterfaceName:
- manager := wlr_gamma_control.NewZwlrGammaControlManagerV1(m.ctx)
- version := e.Version
- if version > 1 {
- version = 1
- }
- if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
- gammaMgr = manager
- }
- case "wl_output":
- output := wlclient.NewOutput(m.ctx)
- version := e.Version
- if version > 4 {
- version = 4
- }
- if err := registry.Bind(e.Name, e.Interface, version, output); err != nil {
- return
- }
- outputID := output.ID()
- output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
- outputNames[outputID] = ev.Name
- })
- if gammaMgr != nil {
- outputs = append(outputs, output)
- m.addAvailableOutput(output)
- }
- m.outputRegNames.Store(outputID, e.Name)
-
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- if enabled && m.controlsInitialized {
- m.post(func() {
- if err := m.addOutputControl(output); err != nil {
- log.Warnf("Failed to add output control: %v", err)
- }
- })
- }
- }
- })
-
- registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
- m.post(func() {
- var foundID uint32
- var foundOut *outputState
- m.outputs.Range(func(id uint32, out *outputState) bool {
- if out.registryName == e.Name {
- foundID = id
- foundOut = out
- return false
- }
- return true
- })
- if foundOut == nil {
- return
- }
- if foundOut.gammaControl != nil {
- foundOut.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1).Destroy()
- foundOut.gammaControl = nil
- }
- m.removeAvailableOutput(foundOut.output)
- if foundOut.output != nil && !foundOut.output.IsZombie() {
- _ = foundOut.output.Release()
- }
- m.outputs.Delete(foundID)
-
- hasOutputs := false
- m.outputs.Range(func(_ uint32, _ *outputState) bool {
- hasOutputs = true
- return false
- })
- if !hasOutputs {
- m.controlsInitialized = false
- }
- })
- })
-
- if err := m.display.Roundtrip(); err != nil {
- return fmt.Errorf("roundtrip 1: %w", err)
- }
- if err := m.display.Roundtrip(); err != nil {
- return fmt.Errorf("roundtrip 2: %w", err)
- }
-
- if gammaMgr == nil {
- return errdefs.ErrNoGammaControl
- }
- if len(outputs) == 0 {
- return fmt.Errorf("no outputs")
- }
-
- physicalOutputs := make([]*wlclient.Output, 0, len(outputs))
- for _, output := range outputs {
- name := outputNames[output.ID()]
- if len(name) >= 9 && name[:9] == "HEADLESS-" {
- continue
- }
- physicalOutputs = append(physicalOutputs, output)
- }
-
- m.gammaControl = gammaMgr
- m.availableOutputs = physicalOutputs
- return nil
-}
-
-func (m *Manager) setupOutputControls(outputs []*wlclient.Output, manager *wlr_gamma_control.ZwlrGammaControlManagerV1) error {
- for _, output := range outputs {
- control, err := manager.GetGammaControl(output)
- if err != nil {
- continue
- }
- outputID := output.ID()
- registryName, _ := m.outputRegNames.Load(outputID)
- outState := &outputState{
- id: outputID,
- registryName: registryName,
- output: output,
- gammaControl: control,
- }
- m.setupControlHandlers(outState, control)
- m.outputs.Store(outputID, outState)
- }
- return nil
-}
-
-func (m *Manager) setupControlHandlers(state *outputState, control *wlr_gamma_control.ZwlrGammaControlV1) {
- outputID := state.id
-
- control.SetGammaSizeHandler(func(e wlr_gamma_control.ZwlrGammaControlV1GammaSizeEvent) {
- size := e.Size
- m.post(func() {
- if out, ok := m.outputs.Load(outputID); ok {
- out.rampSize = size
- out.failed = false
- out.retryCount = 0
- }
- m.lastAppliedTemp = 0
- m.applyCurrentTemp("gamma_size")
- })
- })
-
- control.SetFailedHandler(func(_ wlr_gamma_control.ZwlrGammaControlV1FailedEvent) {
- m.post(func() {
- out, ok := m.outputs.Load(outputID)
- if !ok {
- return
- }
- if ctrl, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1); ok && ctrl != nil && !ctrl.IsZombie() {
- ctrl.Destroy()
- }
- out.gammaControl = nil
- out.failed = true
- out.rampSize = 0
- out.retryCount++
- out.lastFailTime = time.Now()
-
- if !m.outputStillValid(out) {
- return
- }
-
- backoff := time.Duration(300<<uint(min(out.retryCount-1, 4))) * time.Millisecond
- time.AfterFunc(backoff, func() {
- m.post(func() {
- if !m.outputStillValid(out) {
- return
- }
- if _, stillTracked := m.outputs.Load(outputID); !stillTracked {
- return
- }
- m.recreateOutputControl(out)
- })
- })
- })
- })
-}
-
-func (m *Manager) addAvailableOutput(o *wlclient.Output) {
- if o == nil {
- return
- }
- m.availOutputsMu.Lock()
- defer m.availOutputsMu.Unlock()
- if slices.Contains(m.availableOutputs, o) {
- return
- }
- m.availableOutputs = append(m.availableOutputs, o)
-}
-
-func (m *Manager) removeAvailableOutput(o *wlclient.Output) {
- if o == nil {
- return
- }
- m.availOutputsMu.Lock()
- defer m.availOutputsMu.Unlock()
- m.availableOutputs = slices.DeleteFunc(m.availableOutputs, func(existing *wlclient.Output) bool {
- return existing == o
- })
-}
-
-func (m *Manager) outputStillValid(out *outputState) bool {
- switch {
- case out == nil:
- return false
- case out.output == nil:
- return false
- case out.output.IsZombie():
- return false
- }
- m.availOutputsMu.RLock()
- defer m.availOutputsMu.RUnlock()
- return slices.Contains(m.availableOutputs, out.output)
-}
-
-func isConnectionDeadErr(err error) bool {
- switch {
- case err == nil:
- return false
- case errors.Is(err, syscall.EPIPE):
- return true
- case errors.Is(err, syscall.ECONNRESET):
- return true
- case errors.Is(err, syscall.EBADF):
- return true
- case errors.Is(err, io.EOF):
- return true
- }
- return false
-}
-
-func (m *Manager) addOutputControl(output *wlclient.Output) error {
- switch {
- case m.connectionDead.Load():
- return nil
- case output == nil || output.IsZombie():
- return nil
- }
-
- outputID := output.ID()
- gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
-
- control, err := gammaMgr.GetGammaControl(output)
- if err != nil {
- if isConnectionDeadErr(err) {
- m.markConnectionDead(err)
- }
- return err
- }
-
- registryName, _ := m.outputRegNames.Load(outputID)
- outState := &outputState{
- id: outputID,
- registryName: registryName,
- output: output,
- gammaControl: control,
- }
- m.setupControlHandlers(outState, control)
- m.outputs.Store(outputID, outState)
- return nil
-}
-
-func (m *Manager) recreateOutputControl(out *outputState) error {
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- switch {
- case m.connectionDead.Load():
- return nil
- case !enabled || !m.controlsInitialized:
- return nil
- case out.isVirtual:
- return nil
- case out.retryCount >= 10:
- return nil
- case !m.outputStillValid(out):
- return nil
- }
- if _, ok := m.outputs.Load(out.id); !ok {
- return nil
- }
-
- gammaMgr, ok := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
- if !ok {
- return fmt.Errorf("no gamma manager")
- }
-
- if existing, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1); ok && existing != nil && !existing.IsZombie() {
- existing.Destroy()
- out.gammaControl = nil
- }
-
- control, err := gammaMgr.GetGammaControl(out.output)
- if err != nil {
- if isConnectionDeadErr(err) {
- m.markConnectionDead(err)
- }
- return err
- }
-
- m.setupControlHandlers(out, control)
- out.gammaControl = control
- out.failed = false
- return nil
-}
-
-func (m *Manager) markConnectionDead(err error) {
- if m.connectionDead.Swap(true) {
- return
- }
- log.Errorf("gamma: wayland connection appears dead (%v); pausing gamma operations", err)
-}
-
-func (m *Manager) recalcSchedule(now time.Time) {
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- m.scheduleMutex.Lock()
- defer m.scheduleMutex.Unlock()
-
- dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- alreadyValid := !m.schedule.times.Sunrise.IsZero()
- if m.schedule.calcDay.Equal(dayStart) && alreadyValid {
- return
- }
-
- var times SunTimes
- var cond SunCondition
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- dur := time.Hour
- if config.ManualDuration != nil {
- dur = *config.ManualDuration
- }
- sunrise := time.Date(now.Year(), now.Month(), now.Day(),
- config.ManualSunrise.Hour(), config.ManualSunrise.Minute(), config.ManualSunrise.Second(), 0, now.Location())
- sunset := time.Date(now.Year(), now.Month(), now.Day(),
- config.ManualSunset.Hour(), config.ManualSunset.Minute(), config.ManualSunset.Second(), 0, now.Location())
- times = SunTimes{
- Dawn: sunrise.Add(-dur),
- Sunrise: sunrise,
- Sunset: sunset,
- Night: sunset.Add(dur),
- }
- cond = SunNormal
- } else {
- lat, lon := m.getLocation()
- if lat == nil || lon == nil {
- m.gammaState = StateStatic
- return
- }
- times, cond = CalculateSunTimesWithTwilight(*lat, *lon, now, config.ElevationTwilight, config.ElevationDaylight)
- }
-
- m.schedule.calcDay = dayStart
- m.schedule.times = times
- m.schedule.condition = cond
-
- switch cond {
- case SunNormal:
- m.gammaState = StateNormal
- tempDiff := config.HighTemp - config.LowTemp
- if tempDiff > 0 {
- dawnDur := times.Sunrise.Sub(times.Dawn)
- nightDur := times.Night.Sub(times.Sunset)
- m.schedule.dawnStepTime = time.Duration(max(1, int(dawnDur.Seconds())*animKelvinStep/tempDiff)) * time.Second
- m.schedule.nightStepTime = time.Duration(max(1, int(nightDur.Seconds())*animKelvinStep/tempDiff)) * time.Second
- }
- case SunMidnightSun:
- m.gammaState = StateStatic
- case SunPolarNight:
- m.gammaState = StateStatic
- }
-}
-
-func (m *Manager) SetGeoClient(client geolocation.Client) {
- m.geoClient = client
-}
-
-func (m *Manager) getLocation() (*float64, *float64) {
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- if config.Latitude != nil && config.Longitude != nil {
- return config.Latitude, config.Longitude
- }
- if !config.UseIPLocation {
- return nil, nil
- }
- if m.geoClient == nil {
- return nil, nil
- }
-
- m.locationMutex.RLock()
- if m.cachedIPLat != nil && m.cachedIPLon != nil {
- lat, lon := m.cachedIPLat, m.cachedIPLon
- m.locationMutex.RUnlock()
- return lat, lon
- }
- m.locationMutex.RUnlock()
-
- location, err := m.geoClient.GetLocation()
- if err != nil {
- return nil, nil
- }
-
- m.locationMutex.Lock()
- m.cachedIPLat = &location.Latitude
- m.cachedIPLon = &location.Longitude
- m.locationMutex.Unlock()
- return m.cachedIPLat, m.cachedIPLon
-}
-
-func (m *Manager) hasValidSchedule() bool {
- m.scheduleMutex.RLock()
- defer m.scheduleMutex.RUnlock()
- return !m.schedule.times.Sunrise.IsZero()
-}
-
-func (m *Manager) getSunPosition(now time.Time) float64 {
- m.scheduleMutex.RLock()
- sched := m.schedule
- state := m.gammaState
- m.scheduleMutex.RUnlock()
-
- if sched.times.Sunrise.IsZero() {
- return 1.0
- }
-
- switch state {
- case StateStatic:
- if sched.condition == SunMidnightSun {
- return 1.0
- }
- return 0.0
- case StateNormal:
- return m.getSunPositionNormal(now, sched.times)
- }
- return 1.0
-}
-
-func (m *Manager) getSunPositionNormal(now time.Time, times SunTimes) float64 {
- if now.Before(times.Dawn) {
- return 0.0
- }
- if now.Before(times.Sunrise) {
- return interpolate(now, times.Dawn, times.Sunrise)
- }
- if now.Before(times.Sunset) {
- return 1.0
- }
- if now.Before(times.Night) {
- return interpolate(now, times.Night, times.Sunset)
- }
- return 0.0
-}
-
-func interpolate(now time.Time, start, stop time.Time) float64 {
- if start.Equal(stop) {
- return 1.0
- }
- pos := float64(now.Sub(start)) / float64(stop.Sub(start))
- switch {
- case pos > 1.0:
- return 1.0
- case pos < 0.0:
- return 0.0
- default:
- return pos
- }
-}
-
-func (m *Manager) getTempFromPosition(pos float64) int {
- m.configMutex.RLock()
- low, high := m.config.LowTemp, m.config.HighTemp
- m.configMutex.RUnlock()
- return low + int(float64(high-low)*pos)
-}
-
-func (m *Manager) getNextDeadline(now time.Time) time.Time {
- m.scheduleMutex.RLock()
- sched := m.schedule
- state := m.gammaState
- m.scheduleMutex.RUnlock()
-
- switch state {
- case StateStatic:
- return m.tomorrow(now)
- case StateNormal:
- return m.getDeadlineNormal(now, sched)
- default:
- return m.tomorrow(now)
- }
-}
-
-func (m *Manager) getDeadlineNormal(now time.Time, sched sunSchedule) time.Time {
- times := sched.times
- switch {
- case now.Before(times.Dawn):
- return times.Dawn
- case now.Before(times.Sunrise):
- return now.Add(sched.dawnStepTime)
- case now.Before(times.Sunset):
- return times.Sunset
- case now.Before(times.Night):
- return now.Add(sched.nightStepTime)
- default:
- return m.tomorrowDawn(now)
- }
-}
-
-func (m *Manager) tomorrowDawn(now time.Time) time.Time {
- tomorrow := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
-
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- if config.ManualSunrise != nil {
- dur := time.Hour
- if config.ManualDuration != nil {
- dur = *config.ManualDuration
- }
- return time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(),
- config.ManualSunrise.Hour(), config.ManualSunrise.Minute(), config.ManualSunrise.Second(), 0, tomorrow.Location()).Add(-dur)
- }
-
- lat, lon := m.getLocation()
- if lat == nil || lon == nil {
- return tomorrow
- }
-
- times, cond := CalculateSunTimesWithTwilight(*lat, *lon, tomorrow, config.ElevationTwilight, config.ElevationDaylight)
- if cond != SunNormal {
- return tomorrow
- }
- return times.Dawn
-}
-
-func (m *Manager) tomorrow(now time.Time) time.Time {
- return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
-}
-
-func (m *Manager) schedulerLoop() {
- defer m.wg.Done()
-
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- if enabled {
- m.post(func() { m.applyCurrentTemp("startup") })
- }
-
- var timer *time.Timer
- for {
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- now := time.Now()
- m.recalcSchedule(now)
-
- var waitDur time.Duration
- if enabled {
- deadline := m.getNextDeadline(now)
- waitDur = time.Until(deadline)
- if waitDur < time.Second {
- waitDur = time.Second
- }
- } else {
- waitDur = 24 * time.Hour
- }
-
- if timer != nil {
- timer.Stop()
- }
- timer = time.NewTimer(waitDur)
-
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.updateTrigger:
- timer.Stop()
- m.scheduleMutex.Lock()
- m.schedule.calcDay = time.Time{}
- m.scheduleMutex.Unlock()
- m.recalcSchedule(time.Now())
- m.updateStateFromSchedule()
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
- if enabled {
- m.post(func() { m.applyCurrentTemp("updateTrigger") })
- }
- case <-timer.C:
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
- if enabled {
- m.post(func() { m.applyCurrentTemp("timer") })
- }
- }
- }
-}
-
-func (m *Manager) applyCurrentTemp(_ string) {
- if !m.controlsInitialized || !m.anyOutputReady() {
- return
- }
-
- // Ensure schedule is up-to-date (handles display wake after overnight sleep)
- m.recalcSchedule(time.Now())
-
- m.configMutex.RLock()
- low, high := m.config.LowTemp, m.config.HighTemp
- m.configMutex.RUnlock()
-
- if low == high {
- m.applyGamma(low)
- m.updateStateFromSchedule()
- return
- }
-
- if !m.hasValidSchedule() {
- m.updateStateFromSchedule()
- return
- }
-
- now := time.Now()
- pos := m.getSunPosition(now)
- temp := m.getTempFromPosition(pos)
-
- m.applyGamma(temp)
- m.updateStateFromSchedule()
-}
-
-func (m *Manager) applyGamma(temp int) {
- m.configMutex.RLock()
- gamma := m.config.Gamma
- m.configMutex.RUnlock()
-
- switch {
- case m.connectionDead.Load():
- return
- case !m.controlsInitialized:
- return
- case m.lastAppliedTemp == temp && m.lastAppliedGamma == gamma:
- return
- }
-
- var outs []*outputState
- m.outputs.Range(func(_ uint32, out *outputState) bool {
- outs = append(outs, out)
- return true
- })
- if len(outs) == 0 {
- return
- }
-
- type job struct {
- out *outputState
- data []byte
- }
- var jobs []job
-
- for _, out := range outs {
- switch {
- case out.failed:
- continue
- case out.rampSize == 0:
- continue
- case out.gammaControl == nil:
- continue
- case !m.outputStillValid(out):
- continue
- }
- ramp := GenerateGammaRamp(out.rampSize, temp, gamma)
- buf := bytes.NewBuffer(make([]byte, 0, int(out.rampSize)*6))
- for _, v := range ramp.Red {
- binary.Write(buf, binary.LittleEndian, v)
- }
- for _, v := range ramp.Green {
- binary.Write(buf, binary.LittleEndian, v)
- }
- for _, v := range ramp.Blue {
- binary.Write(buf, binary.LittleEndian, v)
- }
- jobs = append(jobs, job{out: out, data: buf.Bytes()})
- }
-
- for _, j := range jobs {
- err := m.setGammaBytes(j.out, j.data)
- if err == nil {
- continue
- }
- log.Warnf("gamma: failed to set output %d: %v", j.out.id, err)
- j.out.failed = true
- j.out.rampSize = 0
- if isConnectionDeadErr(err) {
- m.markConnectionDead(err)
- return
- }
- }
-
- m.lastAppliedTemp = temp
- m.lastAppliedGamma = gamma
-}
-
-func (m *Manager) setGammaBytes(out *outputState, data []byte) error {
- if out.gammaControl == nil {
- return fmt.Errorf("no gamma control")
- }
- ctrl, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1)
- if !ok || ctrl == nil || ctrl.IsZombie() {
- return fmt.Errorf("gamma control invalid")
- }
-
- fd, err := MemfdCreate("gamma-ramp", 0)
- if err != nil {
- return err
- }
- defer syscall.Close(fd)
-
- if err := syscall.Ftruncate(fd, int64(len(data))); err != nil {
- return err
- }
-
- dupFd, err := syscall.Dup(fd)
- if err != nil {
- return err
- }
- f := os.NewFile(uintptr(dupFd), "gamma")
- defer f.Close()
-
- if _, err := f.Write(data); err != nil {
- return err
- }
- syscall.Seek(fd, 0, 0)
-
- return ctrl.SetGamma(fd)
-}
-
-func (m *Manager) updateStateFromSchedule() {
- now := time.Now()
-
- m.configMutex.RLock()
- config := m.config
- m.configMutex.RUnlock()
-
- m.scheduleMutex.RLock()
- times := m.schedule.times
- m.scheduleMutex.RUnlock()
-
- var pos float64
- var temp int
- var isDay bool
- var deadline time.Time
-
- if times.Sunrise.IsZero() {
- pos = 1.0
- temp = config.HighTemp
- isDay = true
- deadline = m.tomorrow(now)
- } else {
- pos = m.getSunPosition(now)
- temp = m.getTempFromPosition(pos)
- deadline = m.getNextDeadline(now)
- isDay = now.After(times.Sunrise) && now.Before(times.Sunset)
- }
-
- newState := State{
- Config: config,
- CurrentTemp: temp,
- NextTransition: deadline,
- SunriseTime: times.Sunrise,
- SunsetTime: times.Sunset,
- DawnTime: times.Dawn,
- NightTime: times.Night,
- IsDay: isDay,
- SunPosition: pos,
- }
-
- m.stateMutex.Lock()
- m.state = &newState
- m.stateMutex.Unlock()
-
- m.notifySubscribers()
-}
-
-func (m *Manager) notifier() {
- defer m.notifierWg.Done()
- const minGap = 100 * time.Millisecond
- timer := time.NewTimer(minGap)
- timer.Stop()
- var pending bool
-
- for {
- select {
- case <-m.stopChan:
- timer.Stop()
- return
- case <-m.dirty:
- if pending {
- continue
- }
- pending = true
- timer.Reset(minGap)
- case <-timer.C:
- if !pending {
- continue
- }
- currentState := m.GetState()
- if m.lastNotified != nil && !stateChanged(m.lastNotified, &currentState) {
- pending = false
- continue
- }
- m.subscribers.Range(func(_ string, ch chan State) bool {
- select {
- case ch <- currentState:
- default:
- }
- return true
- })
- stateCopy := currentState
- m.lastNotified = &stateCopy
- pending = false
- }
- }
-}
-
-func (m *Manager) dbusMonitor() {
- defer m.wg.Done()
- for {
- select {
- case <-m.stopChan:
- return
- case sig := <-m.dbusSignal:
- if sig == nil {
- continue
- }
- m.handleDBusSignal(sig)
- }
- }
-}
-
-func (m *Manager) handleDBusSignal(sig *dbus.Signal) {
- switch {
- case sig.Name != "org.freedesktop.login1.Manager.PrepareForSleep":
- return
- case len(sig.Body) == 0:
- return
- }
- preparing, ok := sig.Body[0].(bool)
- if !ok || preparing {
- return
- }
- m.configMutex.RLock()
- enabled := m.config.Enabled
- m.configMutex.RUnlock()
- if !enabled {
- return
- }
- time.AfterFunc(500*time.Millisecond, func() {
- m.post(m.handleResume)
- })
-}
-
-func (m *Manager) handleResume() {
- m.configMutex.RLock()
- stillEnabled := m.config.Enabled
- m.configMutex.RUnlock()
-
- switch {
- case !stillEnabled:
- return
- case !m.controlsInitialized:
- return
- case m.connectionDead.Load():
- return
- }
-
- // Compositors (Niri, Hyprland, wlroots-based) re-apply the cached gamma
- // ramp to DRM on resume; gamma_control objects stay valid. We just need
- // to force a resend so the schedule catches up with the current time of
- // day — the original #1235 regression was caused by lastAppliedTemp
- // matching and the send being skipped.
- m.recalcSchedule(time.Now())
- m.lastAppliedTemp = 0
- m.applyCurrentTemp("resume")
-}
-
-func (m *Manager) triggerUpdate() {
- select {
- case m.updateTrigger <- struct{}{}:
- default:
- }
-}
-
-func (m *Manager) SetConfig(config Config) error {
- if err := config.Validate(); err != nil {
- return err
- }
- m.configMutex.Lock()
- m.config = config
- m.configMutex.Unlock()
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetTemperature(low, high int) error {
- m.configMutex.Lock()
- if m.config.LowTemp == low && m.config.HighTemp == high {
- m.configMutex.Unlock()
- return nil
- }
- m.config.LowTemp = low
- m.config.HighTemp = high
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetLocation(lat, lon float64) error {
- m.configMutex.Lock()
- if m.config.Latitude != nil && m.config.Longitude != nil &&
- *m.config.Latitude == lat && *m.config.Longitude == lon && !m.config.UseIPLocation {
- m.configMutex.Unlock()
- return nil
- }
- m.config.Latitude = &lat
- m.config.Longitude = &lon
- m.config.UseIPLocation = false
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetUseIPLocation(use bool) {
- m.configMutex.Lock()
- if m.config.UseIPLocation == use {
- m.configMutex.Unlock()
- return
- }
- m.config.UseIPLocation = use
- if use {
- m.config.Latitude = nil
- m.config.Longitude = nil
- }
- m.configMutex.Unlock()
-
- if use {
- m.locationMutex.Lock()
- m.cachedIPLat = nil
- m.cachedIPLon = nil
- m.locationMutex.Unlock()
- }
- m.triggerUpdate()
-}
-
-func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
- m.configMutex.Lock()
- if m.config.ManualSunrise != nil && m.config.ManualSunset != nil &&
- m.config.ManualSunrise.Hour() == sunrise.Hour() && m.config.ManualSunrise.Minute() == sunrise.Minute() &&
- m.config.ManualSunset.Hour() == sunset.Hour() && m.config.ManualSunset.Minute() == sunset.Minute() {
- m.configMutex.Unlock()
- return nil
- }
- m.config.ManualSunrise = &sunrise
- m.config.ManualSunset = &sunset
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) ClearManualTimes() {
- m.configMutex.Lock()
- if m.config.ManualSunrise == nil && m.config.ManualSunset == nil {
- m.configMutex.Unlock()
- return
- }
- m.config.ManualSunrise = nil
- m.config.ManualSunset = nil
- m.configMutex.Unlock()
- m.triggerUpdate()
-}
-
-func (m *Manager) SetGamma(gamma float64) error {
- m.configMutex.Lock()
- if m.config.Gamma == gamma {
- m.configMutex.Unlock()
- return nil
- }
- m.config.Gamma = gamma
- err := m.config.Validate()
- m.configMutex.Unlock()
- if err != nil {
- return err
- }
- m.triggerUpdate()
- return nil
-}
-
-func (m *Manager) SetEnabled(enabled bool) {
- m.configMutex.Lock()
- wasEnabled := m.config.Enabled
- if wasEnabled == enabled {
- m.configMutex.Unlock()
- return
- }
- m.config.Enabled = enabled
- highTemp := m.config.HighTemp
- m.configMutex.Unlock()
-
- switch {
- case enabled && !m.controlsInitialized:
- m.post(func() {
- gammaMgr := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1)
- m.availOutputsMu.RLock()
- outs := slices.Clone(m.availableOutputs)
- m.availOutputsMu.RUnlock()
- if err := m.setupOutputControls(outs, gammaMgr); err != nil {
- log.Errorf("gamma: failed to create controls: %v", err)
- return
- }
- m.controlsInitialized = true
- m.triggerUpdate()
- })
- case enabled && !wasEnabled:
- m.triggerUpdate()
- case !enabled && m.controlsInitialized:
- m.post(func() {
- m.outputs.Range(func(id uint32, out *outputState) bool {
- if out.gammaControl != nil {
- out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1).Destroy()
- }
- return true
- })
- m.outputs.Range(func(key uint32, _ *outputState) bool {
- m.outputs.Delete(key)
- return true
- })
- m.controlsInitialized = false
- })
- _ = highTemp
- }
-}
-
-func (m *Manager) Close() {
- close(m.stopChan)
- m.wg.Wait()
- m.notifierWg.Wait()
-
- m.subscribers.Range(func(key string, ch chan State) bool {
- close(ch)
- m.subscribers.Delete(key)
- return true
- })
-
- m.outputs.Range(func(_ uint32, out *outputState) bool {
- if ctrl, ok := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1); ok {
- ctrl.Destroy()
- }
- return true
- })
- m.outputs.Range(func(key uint32, _ *outputState) bool {
- m.outputs.Delete(key)
- return true
- })
-
- if manager, ok := m.gammaControl.(*wlr_gamma_control.ZwlrGammaControlManagerV1); ok {
- manager.Destroy()
- }
-
- if m.dbusConn != nil {
- m.dbusConn.RemoveSignal(m.dbusSignal)
- m.dbusConn.Close()
- }
-}
-
-func MemfdCreate(name string, flags int) (int, error) {
- fd, err := unix.MemfdCreate(name, flags)
- if err != nil {
- return -1, err
- }
- return fd, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go
deleted file mode 100644
index 8c00129..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/manager_test.go
+++ /dev/null
@@ -1,414 +0,0 @@
-package wayland
-
-import (
- "errors"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-
- mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
-)
-
-func TestManager_ActorSerializesOutputStateAccess(t *testing.T) {
- m := &Manager{
- cmdq: make(chan cmd, 8192),
- stopChan: make(chan struct{}),
- }
-
- m.wg.Add(1)
- go m.waylandActor()
-
- state := &outputState{
- id: 1,
- registryName: 100,
- rampSize: 256,
- }
- m.outputs.Store(state.id, state)
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.post(func() {
- if out, ok := m.outputs.Load(state.id); ok {
- out.rampSize = uint32(j)
- out.failed = j%2 == 0
- out.retryCount = j
- out.lastFailTime = time.Now()
- }
- })
- }
- }(i)
- }
-
- wg.Wait()
-
- done := make(chan struct{})
- m.post(func() { close(done) })
- <-done
-
- close(m.stopChan)
- m.wg.Wait()
-}
-
-func TestManager_ConcurrentSubscriberAccess(t *testing.T) {
- m := &Manager{
- stopChan: make(chan struct{}),
- dirty: make(chan struct{}, 1),
- updateTrigger: make(chan struct{}, 1),
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- subID := string(rune('a' + id))
- ch := m.Subscribe(subID)
- assert.NotNil(t, ch)
- time.Sleep(time.Millisecond)
- m.Unsubscribe(subID)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentGetState(t *testing.T) {
- m := &Manager{
- state: &State{
- CurrentTemp: 5000,
- IsDay: true,
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 50
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- s := m.GetState()
- assert.GreaterOrEqual(t, s.CurrentTemp, 0)
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.stateMutex.Lock()
- m.state = &State{
- CurrentTemp: 4000 + i*100,
- IsDay: j%2 == 0,
- }
- m.stateMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ConcurrentConfigAccess(t *testing.T) {
- m := &Manager{
- config: DefaultConfig(),
- }
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.configMutex.RLock()
- _ = m.config.LowTemp
- _ = m.config.HighTemp
- _ = m.config.Enabled
- m.configMutex.RUnlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.configMutex.Lock()
- m.config.LowTemp = 3000 + j
- m.config.HighTemp = 7000 - j
- m.config.Enabled = j%2 == 0
- m.configMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_SyncmapOutputsConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 30
- const iterations = 50
-
- for i := 0; i < goroutines; i++ {
- wg.Add(1)
- go func(id int) {
- defer wg.Done()
- key := uint32(id)
-
- for j := 0; j < iterations; j++ {
- state := &outputState{
- id: key,
- rampSize: uint32(j),
- failed: j%2 == 0,
- }
- m.outputs.Store(key, state)
-
- if loaded, ok := m.outputs.Load(key); ok {
- assert.Equal(t, key, loaded.id)
- }
-
- m.outputs.Range(func(k uint32, v *outputState) bool {
- _ = v.rampSize
- _ = v.failed
- return true
- })
- }
-
- m.outputs.Delete(key)
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_LocationCacheConcurrentAccess(t *testing.T) {
- m := &Manager{}
-
- var wg sync.WaitGroup
- const goroutines = 20
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.locationMutex.RLock()
- _ = m.cachedIPLat
- _ = m.cachedIPLon
- m.locationMutex.RUnlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- lat := float64(40 + i)
- lon := float64(-74 + j)
- m.locationMutex.Lock()
- m.cachedIPLat = &lat
- m.cachedIPLon = &lon
- m.locationMutex.Unlock()
- }
- }(i)
- }
-
- wg.Wait()
-}
-
-func TestManager_ScheduleConcurrentAccess(t *testing.T) {
- now := time.Now()
- m := &Manager{
- schedule: sunSchedule{
- times: SunTimes{
- Dawn: now,
- Sunrise: now.Add(time.Hour),
- Sunset: now.Add(12 * time.Hour),
- Night: now.Add(13 * time.Hour),
- },
- },
- }
-
- var wg sync.WaitGroup
- const goroutines = 20
- const iterations = 100
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.scheduleMutex.RLock()
- _ = m.schedule.times.Dawn
- _ = m.schedule.times.Sunrise
- _ = m.schedule.times.Sunset
- _ = m.schedule.condition
- m.scheduleMutex.RUnlock()
- }
- }()
- }
-
- for i := 0; i < goroutines/2; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for j := 0; j < iterations; j++ {
- m.scheduleMutex.Lock()
- m.schedule.times.Dawn = time.Now()
- m.schedule.times.Sunrise = time.Now().Add(time.Hour)
- m.schedule.condition = SunNormal
- m.scheduleMutex.Unlock()
- }
- }()
- }
-
- wg.Wait()
-}
-
-func TestInterpolate_EdgeCases(t *testing.T) {
- now := time.Now()
-
- tests := []struct {
- name string
- now time.Time
- start time.Time
- stop time.Time
- expected float64
- }{
- {
- name: "same start and stop",
- now: now,
- start: now,
- stop: now,
- expected: 1.0,
- },
- {
- name: "now before start",
- now: now,
- start: now.Add(time.Hour),
- stop: now.Add(2 * time.Hour),
- expected: 0.0,
- },
- {
- name: "now after stop",
- now: now.Add(3 * time.Hour),
- start: now,
- stop: now.Add(time.Hour),
- expected: 1.0,
- },
- {
- name: "now at midpoint",
- now: now.Add(30 * time.Minute),
- start: now,
- stop: now.Add(time.Hour),
- expected: 0.5,
- },
- {
- name: "now equals start",
- now: now,
- start: now,
- stop: now.Add(time.Hour),
- expected: 0.0,
- },
- {
- name: "now equals stop",
- now: now.Add(time.Hour),
- start: now,
- stop: now.Add(time.Hour),
- expected: 1.0,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := interpolate(tt.now, tt.start, tt.stop)
- assert.InDelta(t, tt.expected, result, 0.01)
- })
- }
-}
-
-func TestGenerateGammaRamp_ZeroSize(t *testing.T) {
- ramp := GenerateGammaRamp(0, 5000, 1.0)
- assert.Empty(t, ramp.Red)
- assert.Empty(t, ramp.Green)
- assert.Empty(t, ramp.Blue)
-}
-
-func TestGenerateGammaRamp_ValidSizes(t *testing.T) {
- sizes := []uint32{1, 256, 1024}
- temps := []int{1000, 4000, 6500, 10000}
- gammas := []float64{0.5, 1.0, 2.0}
-
- for _, size := range sizes {
- for _, temp := range temps {
- for _, gamma := range gammas {
- ramp := GenerateGammaRamp(size, temp, gamma)
- assert.Len(t, ramp.Red, int(size))
- assert.Len(t, ramp.Green, int(size))
- assert.Len(t, ramp.Blue, int(size))
- }
- }
- }
-}
-
-func TestNotifySubscribers_NonBlocking(t *testing.T) {
- m := &Manager{
- dirty: make(chan struct{}, 1),
- }
-
- for i := 0; i < 10; i++ {
- m.notifySubscribers()
- }
-
- assert.Len(t, m.dirty, 1)
-}
-
-func TestNewManager_GetRegistryError(t *testing.T) {
- mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
-
- mockDisplay.EXPECT().Context().Return(nil)
- mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry"))
-
- config := DefaultConfig()
- _, err := NewManager(mockDisplay, config)
- assert.Error(t, err)
- assert.Contains(t, err.Error(), "get registry")
-}
-
-func TestNewManager_InvalidConfig(t *testing.T) {
- mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t)
-
- config := Config{
- LowTemp: 500,
- HighTemp: 6500,
- Gamma: 1.0,
- }
-
- _, err := NewManager(mockDisplay, config)
- assert.Error(t, err)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go
deleted file mode 100644
index c978a8e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package wayland
-
-import (
- "math"
- "time"
-)
-
-const (
- degToRad = math.Pi / 180.0
- radToDeg = 180.0 / math.Pi
-)
-
-type SunCondition int
-
-const (
- SunNormal SunCondition = iota
- SunMidnightSun
- SunPolarNight
-)
-
-type SunTimes struct {
- Dawn time.Time
- Sunrise time.Time
- Sunset time.Time
- Night time.Time
-}
-
-func daysInYear(year int) int {
- if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
- return 366
- }
- return 365
-}
-
-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
-
- utc := date.UTC()
- orbitAngle := dateOrbitAngle(utc)
- decl := sunDeclination(orbitAngle)
- eqtime := equationOfTime(orbitAngle)
-
- haTwilight := sunHourAngle(latRad, decl, elevTwilightRad)
- haDaylight := sunHourAngle(latRad, decl, elevDaylightRad)
-
- if math.IsNaN(haTwilight) || math.IsNaN(haDaylight) {
- cond := sunCondition(latRad, decl)
- return SunTimes{}, cond
- }
-
- dayStart := time.Date(utc.Year(), utc.Month(), utc.Day(), 0, 0, 0, 0, time.UTC)
- lonOffset := time.Duration(-lon*4) * time.Minute
-
- 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{
- 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 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}
- }
- return times
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go
deleted file mode 100644
index b2815d9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/suncalc_test.go
+++ /dev/null
@@ -1,387 +0,0 @@
-package wayland
-
-import (
- "math"
- "testing"
- "time"
-)
-
-func calculateTemperature(config Config, now time.Time) int {
- if !config.Enabled {
- return config.HighTemp
- }
-
- var sunrise, sunset time.Time
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- year, month, day := now.Date()
- loc := now.Location()
-
- sunrise = time.Date(year, month, day,
- config.ManualSunrise.Hour(),
- config.ManualSunrise.Minute(),
- config.ManualSunrise.Second(), 0, loc)
- sunset = time.Date(year, month, day,
- config.ManualSunset.Hour(),
- config.ManualSunset.Minute(),
- config.ManualSunset.Second(), 0, loc)
-
- if sunset.Before(sunrise) {
- sunset = sunset.Add(24 * time.Hour)
- }
- } else if config.UseIPLocation {
- lat, lon, err := FetchIPLocation()
- if err != nil {
- return config.HighTemp
- }
- times := CalculateSunTimes(*lat, *lon, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else if config.Latitude != nil && config.Longitude != nil {
- times := CalculateSunTimes(*config.Latitude, *config.Longitude, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else {
- return config.HighTemp
- }
-
- if now.Before(sunrise) || now.After(sunset) {
- return config.LowTemp
- }
- return config.HighTemp
-}
-
-func calculateNextTransition(config Config, now time.Time) time.Time {
- if !config.Enabled {
- return now.Add(24 * time.Hour)
- }
-
- var sunrise, sunset time.Time
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- year, month, day := now.Date()
- loc := now.Location()
-
- sunrise = time.Date(year, month, day,
- config.ManualSunrise.Hour(),
- config.ManualSunrise.Minute(),
- config.ManualSunrise.Second(), 0, loc)
- sunset = time.Date(year, month, day,
- config.ManualSunset.Hour(),
- config.ManualSunset.Minute(),
- config.ManualSunset.Second(), 0, loc)
-
- if sunset.Before(sunrise) {
- sunset = sunset.Add(24 * time.Hour)
- }
- } else if config.UseIPLocation {
- lat, lon, err := FetchIPLocation()
- if err != nil {
- return now.Add(24 * time.Hour)
- }
- times := CalculateSunTimes(*lat, *lon, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else if config.Latitude != nil && config.Longitude != nil {
- times := CalculateSunTimes(*config.Latitude, *config.Longitude, now)
- sunrise = times.Sunrise
- sunset = times.Sunset
- } else {
- return now.Add(24 * time.Hour)
- }
-
- if now.Before(sunrise) {
- return sunrise
- }
- if now.Before(sunset) {
- return sunset
- }
-
- if config.ManualSunrise != nil && config.ManualSunset != nil {
- year, month, day := now.Add(24 * time.Hour).Date()
- loc := now.Location()
- nextSunrise := time.Date(year, month, day,
- config.ManualSunrise.Hour(),
- config.ManualSunrise.Minute(),
- config.ManualSunrise.Second(), 0, loc)
- return nextSunrise
- }
-
- if config.UseIPLocation {
- lat, lon, err := FetchIPLocation()
- if err != nil {
- return now.Add(24 * time.Hour)
- }
- nextDayTimes := CalculateSunTimes(*lat, *lon, now.Add(24*time.Hour))
- return nextDayTimes.Sunrise
- }
-
- if config.Latitude != nil && config.Longitude != nil {
- nextDayTimes := CalculateSunTimes(*config.Latitude, *config.Longitude, now.Add(24*time.Hour))
- return nextDayTimes.Sunrise
- }
-
- return now.Add(24 * time.Hour)
-}
-
-func TestCalculateSunTimes(t *testing.T) {
- tests := []struct {
- name string
- lat float64
- lon float64
- date time.Time
- checkFunc func(*testing.T, SunTimes)
- }{
- {
- name: "new_york_summer",
- lat: 40.7128,
- lon: -74.0060,
- date: time.Date(2024, 6, 21, 12, 0, 0, 0, time.Local),
- checkFunc: func(t *testing.T, times SunTimes) {
- if times.Sunrise.Hour() < 4 || times.Sunrise.Hour() > 6 {
- t.Logf("sunrise: %v", times.Sunrise)
- }
- if times.Sunset.Hour() < 19 || times.Sunset.Hour() > 21 {
- t.Logf("sunset: %v", times.Sunset)
- }
- if !times.Sunset.After(times.Sunrise) {
- t.Error("sunset should be after sunrise")
- }
- },
- },
- {
- name: "london_winter",
- lat: 51.5074,
- lon: -0.1278,
- date: time.Date(2024, 12, 21, 12, 0, 0, 0, time.UTC),
- checkFunc: func(t *testing.T, times SunTimes) {
- if times.Sunrise.Hour() < 7 || times.Sunrise.Hour() > 9 {
- t.Errorf("unexpected sunrise hour: %d", times.Sunrise.Hour())
- }
- if times.Sunset.Hour() < 15 || times.Sunset.Hour() > 17 {
- t.Errorf("unexpected sunset hour: %d", times.Sunset.Hour())
- }
- },
- },
- {
- name: "equator_equinox",
- lat: 0.0,
- lon: 0.0,
- date: time.Date(2024, 3, 20, 12, 0, 0, 0, time.UTC),
- checkFunc: func(t *testing.T, times SunTimes) {
- if times.Sunrise.Hour() < 5 || times.Sunrise.Hour() > 7 {
- t.Errorf("unexpected sunrise hour: %d", times.Sunrise.Hour())
- }
- if times.Sunset.Hour() < 17 || times.Sunset.Hour() > 19 {
- t.Errorf("unexpected sunset hour: %d", times.Sunset.Hour())
- }
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- times := CalculateSunTimes(tt.lat, tt.lon, tt.date)
- tt.checkFunc(t, times)
- })
- }
-}
-
-func TestCalculateTemperature(t *testing.T) {
- lat := 40.7128
- lon := -74.0060
- date := time.Date(2024, 6, 21, 0, 0, 0, 0, time.Local)
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: &lat,
- Longitude: &lon,
- Enabled: true,
- }
-
- times := CalculateSunTimes(lat, lon, date)
-
- tests := []struct {
- name string
- timeFunc func() time.Time
- wantTemp int
- }{
- {
- name: "midnight",
- timeFunc: func() time.Time { return times.Sunrise.Add(-4 * time.Hour) },
- wantTemp: 4000,
- },
- {
- name: "sunrise",
- timeFunc: func() time.Time { return times.Sunrise },
- wantTemp: 6500,
- },
- {
- name: "noon",
- timeFunc: func() time.Time { return times.Sunrise.Add(6 * time.Hour) },
- wantTemp: 6500,
- },
- {
- name: "after_sunset_transition",
- timeFunc: func() time.Time { return times.Sunset.Add(2 * time.Hour) },
- wantTemp: 4000,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- temp := calculateTemperature(config, tt.timeFunc())
-
- if math.Abs(float64(temp-tt.wantTemp)) > 500 {
- t.Errorf("temperature = %d, want approximately %d", temp, tt.wantTemp)
- }
- })
- }
-}
-
-func TestCalculateTemperatureManualTimes(t *testing.T) {
- sunrise := time.Date(0, 1, 1, 6, 30, 0, 0, time.Local)
- sunset := time.Date(0, 1, 1, 18, 30, 0, 0, time.Local)
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunrise: &sunrise,
- ManualSunset: &sunset,
- Enabled: true,
- }
-
- tests := []struct {
- name string
- time time.Time
- want int
- }{
- {"before_sunrise", time.Date(2024, 1, 1, 3, 0, 0, 0, time.Local), 4000},
- {"at_sunrise", time.Date(2024, 1, 1, 6, 30, 0, 0, time.Local), 6500},
- {"midday", time.Date(2024, 1, 1, 12, 0, 0, 0, time.Local), 6500},
- {"at_sunset", time.Date(2024, 1, 1, 18, 30, 0, 0, time.Local), 6500},
- {"after_sunset", time.Date(2024, 1, 1, 22, 0, 0, 0, time.Local), 4000},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- temp := calculateTemperature(config, tt.time)
- if math.Abs(float64(temp-tt.want)) > 500 {
- t.Errorf("temperature = %d, want approximately %d", temp, tt.want)
- }
- })
- }
-}
-
-func TestCalculateTemperatureDisabled(t *testing.T) {
- lat := 40.7128
- lon := -74.0060
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: &lat,
- Longitude: &lon,
- Enabled: false,
- }
-
- temp := calculateTemperature(config, time.Now())
- if temp != 6500 {
- t.Errorf("disabled should return high temp, got %d", temp)
- }
-}
-
-func TestCalculateNextTransition(t *testing.T) {
- lat := 40.7128
- lon := -74.0060
- date := time.Date(2024, 6, 21, 0, 0, 0, 0, time.Local)
-
- config := Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: &lat,
- Longitude: &lon,
- Enabled: true,
- }
-
- times := CalculateSunTimes(lat, lon, date)
-
- tests := []struct {
- name string
- now time.Time
- checkFunc func(*testing.T, time.Time)
- }{
- {
- name: "before_sunrise",
- now: times.Sunrise.Add(-2 * time.Hour),
- checkFunc: func(t *testing.T, next time.Time) {
- if !next.Equal(times.Sunrise) && !next.After(times.Sunrise.Add(-1*time.Minute)) {
- t.Error("next transition should be at or near sunrise")
- }
- },
- },
- {
- name: "after_sunrise",
- now: times.Sunrise.Add(2 * time.Hour),
- checkFunc: func(t *testing.T, next time.Time) {
- if !next.After(times.Sunrise) {
- t.Error("next transition should be after sunrise")
- }
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- next := calculateNextTransition(config, tt.now)
- tt.checkFunc(t, next)
- })
- }
-}
-
-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
- lat float64
- date time.Time
- expected SunCondition
- }{
- {
- 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) {
- _, 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)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go
deleted file mode 100644
index 31bac15..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types.go
+++ /dev/null
@@ -1,216 +0,0 @@
-package wayland
-
-import (
- "math"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
- "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
- "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
- ElevationTwilight float64
- ElevationDaylight float64
-}
-
-type State struct {
- Config Config `json:"config"`
- CurrentTemp int `json:"currentTemp"`
- 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
- state *State
- stateMutex sync.RWMutex
-
- display wlclient.WaylandDisplay
- ctx *wlclient.Context
- registry *wlclient.Registry
- gammaControl any
- availableOutputs []*wlclient.Output
- availOutputsMu sync.RWMutex
- outputRegNames syncmap.Map[uint32, uint32]
- outputs syncmap.Map[uint32, *outputState]
- controlsInitialized bool
- connectionDead atomic.Bool
-
- cmdq chan cmd
- alive bool
-
- stopChan chan struct{}
- updateTrigger chan struct{}
- wg sync.WaitGroup
-
- schedule sunSchedule
- scheduleMutex sync.RWMutex
- gammaState GammaState
-
- cachedIPLat *float64
- cachedIPLon *float64
- locationMutex sync.RWMutex
-
- subscribers syncmap.Map[string, chan State]
- dirty chan struct{}
- notifierWg sync.WaitGroup
- lastNotified *State
-
- dbusConn *dbus.Conn
- dbusSignal chan *dbus.Signal
-
- geoClient geolocation.Client
-
- lastAppliedTemp int
- lastAppliedGamma float64
-}
-
-type outputState struct {
- id uint32
- registryName uint32
- output *wlclient.Output
- gammaControl any
- rampSize uint32
- failed bool
- isVirtual bool
- retryCount int
- lastFailTime time.Time
-}
-
-func DefaultConfig() Config {
- return Config{
- Outputs: []string{},
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 1.0,
- Enabled: false,
- ElevationTwilight: -6.0,
- ElevationDaylight: 3.0,
- }
-}
-
-func (c *Config) Validate() error {
- if c.LowTemp < 1000 || c.LowTemp > 10000 {
- return errdefs.ErrInvalidTemperature
- }
- if c.HighTemp < 1000 || c.HighTemp > 10000 {
- return errdefs.ErrInvalidTemperature
- }
- if c.LowTemp > c.HighTemp {
- return errdefs.ErrInvalidTemperature
- }
- if c.Gamma <= 0 || c.Gamma > 10 {
- return errdefs.ErrInvalidGamma
- }
- if c.Latitude != nil && (math.Abs(*c.Latitude) > 90) {
- return errdefs.ErrInvalidLocation
- }
- if c.Longitude != nil && (math.Abs(*c.Longitude) > 180) {
- return errdefs.ErrInvalidLocation
- }
- if (c.Latitude != nil) != (c.Longitude != nil) {
- return errdefs.ErrInvalidLocation
- }
- if (c.ManualSunrise != nil) != (c.ManualSunset != nil) {
- return errdefs.ErrInvalidManualTimes
- }
- return nil
-}
-
-func (m *Manager) GetState() State {
- m.stateMutex.RLock()
- defer m.stateMutex.RUnlock()
- if m.state == nil {
- return State{}
- }
- return *m.state
-}
-
-func (m *Manager) Subscribe(id string) chan State {
- ch := make(chan State, 64)
- m.subscribers.Store(id, ch)
- return ch
-}
-
-func (m *Manager) Unsubscribe(id string) {
- if val, ok := m.subscribers.LoadAndDelete(id); ok {
- close(val)
- }
-}
-
-func (m *Manager) notifySubscribers() {
- select {
- case m.dirty <- struct{}{}:
- default:
- }
-}
-
-func stateChanged(old, new *State) bool {
- if old == nil || new == nil {
- return true
- }
- if old.CurrentTemp != new.CurrentTemp {
- return true
- }
- if old.IsDay != new.IsDay {
- return true
- }
- if !old.NextTransition.Equal(new.NextTransition) {
- return true
- }
- if !old.SunriseTime.Equal(new.SunriseTime) {
- return true
- }
- if !old.SunsetTime.Equal(new.SunsetTime) {
- return true
- }
- if old.Config.Enabled != new.Config.Enabled {
- return true
- }
- if old.SunPosition != new.SunPosition {
- return true
- }
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go
deleted file mode 100644
index f5bbb42..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wayland/types_test.go
+++ /dev/null
@@ -1,330 +0,0 @@
-package wayland
-
-import (
- "testing"
- "time"
-)
-
-func TestConfigValidate(t *testing.T) {
- tests := []struct {
- name string
- config Config
- wantErr bool
- }{
- {
- name: "valid_default",
- config: DefaultConfig(),
- wantErr: false,
- },
- {
- name: "valid_with_location",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40.7128),
- Longitude: floatPtr(-74.0060),
- Gamma: 1.0,
- Enabled: true,
- },
- wantErr: false,
- },
- {
- name: "valid_manual_times",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunrise: timePtr(time.Date(0, 1, 1, 6, 30, 0, 0, time.Local)),
- ManualSunset: timePtr(time.Date(0, 1, 1, 18, 30, 0, 0, time.Local)),
- Gamma: 1.0,
- Enabled: true,
- },
- wantErr: false,
- },
- {
- name: "invalid_low_temp_too_low",
- config: Config{
- LowTemp: 500,
- HighTemp: 6500,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_low_temp_too_high",
- config: Config{
- LowTemp: 15000,
- HighTemp: 20000,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_high_temp_too_low",
- config: Config{
- LowTemp: 4000,
- HighTemp: 500,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "valid_temps_equal",
- config: Config{
- LowTemp: 5000,
- HighTemp: 5000,
- Gamma: 1.0,
- },
- wantErr: false,
- },
- {
- name: "invalid_temps_reversed",
- config: Config{
- LowTemp: 6500,
- HighTemp: 4000,
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_gamma_zero",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 0,
- },
- wantErr: true,
- },
- {
- name: "invalid_gamma_negative",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: -1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_gamma_too_high",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 15.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_latitude_too_high",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(100),
- Longitude: floatPtr(0),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_latitude_too_low",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(-100),
- Longitude: floatPtr(0),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_longitude_too_high",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40),
- Longitude: floatPtr(200),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_longitude_too_low",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40),
- Longitude: floatPtr(-200),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_latitude_without_longitude",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Latitude: floatPtr(40),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_longitude_without_latitude",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Longitude: floatPtr(-74),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_sunrise_without_sunset",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunrise: timePtr(time.Date(0, 1, 1, 6, 30, 0, 0, time.Local)),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- {
- name: "invalid_sunset_without_sunrise",
- config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- ManualSunset: timePtr(time.Date(0, 1, 1, 18, 30, 0, 0, time.Local)),
- Gamma: 1.0,
- },
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := tt.config.Validate()
- if (err != nil) != tt.wantErr {
- t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
- }
-}
-
-func TestDefaultConfig(t *testing.T) {
- config := DefaultConfig()
-
- if config.LowTemp != 4000 {
- t.Errorf("default low temp = %d, want 4000", config.LowTemp)
- }
- if config.HighTemp != 6500 {
- t.Errorf("default high temp = %d, want 6500", config.HighTemp)
- }
- if config.Gamma != 1.0 {
- t.Errorf("default gamma = %f, want 1.0", config.Gamma)
- }
- if config.Enabled {
- t.Error("default should be disabled")
- }
- if config.Latitude != nil {
- t.Error("default should not have latitude")
- }
- if config.Longitude != nil {
- t.Error("default should not have longitude")
- }
-}
-
-func TestStateChanged(t *testing.T) {
- baseState := &State{
- CurrentTemp: 5000,
- NextTransition: time.Now(),
- SunriseTime: time.Now().Add(6 * time.Hour),
- SunsetTime: time.Now().Add(18 * time.Hour),
- IsDay: true,
- Config: DefaultConfig(),
- }
-
- tests := []struct {
- name string
- old *State
- new *State
- wantChanged bool
- }{
- {
- name: "nil_old",
- old: nil,
- new: baseState,
- wantChanged: true,
- },
- {
- name: "nil_new",
- old: baseState,
- new: nil,
- wantChanged: true,
- },
- {
- name: "same_state",
- old: baseState,
- new: baseState,
- wantChanged: false,
- },
- {
- name: "temp_changed",
- old: baseState,
- new: &State{
- CurrentTemp: 6000,
- NextTransition: baseState.NextTransition,
- SunriseTime: baseState.SunriseTime,
- SunsetTime: baseState.SunsetTime,
- IsDay: baseState.IsDay,
- Config: baseState.Config,
- },
- wantChanged: true,
- },
- {
- name: "is_day_changed",
- old: baseState,
- new: &State{
- CurrentTemp: baseState.CurrentTemp,
- NextTransition: baseState.NextTransition,
- SunriseTime: baseState.SunriseTime,
- SunsetTime: baseState.SunsetTime,
- IsDay: false,
- Config: baseState.Config,
- },
- wantChanged: true,
- },
- {
- name: "enabled_changed",
- old: baseState,
- new: &State{
- CurrentTemp: baseState.CurrentTemp,
- NextTransition: baseState.NextTransition,
- SunriseTime: baseState.SunriseTime,
- SunsetTime: baseState.SunsetTime,
- IsDay: baseState.IsDay,
- Config: Config{
- LowTemp: 4000,
- HighTemp: 6500,
- Gamma: 1.0,
- Enabled: true,
- },
- },
- wantChanged: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- changed := stateChanged(tt.old, tt.new)
- if changed != tt.wantChanged {
- t.Errorf("stateChanged() = %v, want %v", changed, tt.wantChanged)
- }
- })
- }
-}
-
-func floatPtr(f float64) *float64 {
- return &f
-}
-
-func timePtr(t time.Time) *time.Time {
- return &t
-}